Jerry,
On 12/1/21 11:44, Jerry Malcolm wrote:
Chris,
On 11/30/2021 11:41 PM, Jerry Malcolm wrote:
On 11/30/2021 1:58 PM, Christopher Schultz wrote:
Jerry,
On 11/30/21 14:17, Jerry Malcolm wrote:
Chris, Thanks for the response.
Sorry... forgot to include the TC ver -- 8.5.69.
I had a situation a while back where I spun a longrunning thread and
held the request object after the main response was returned. I
fixed that situation. In this situation, it is occurring on the
main request/response thread. Is there any situation where the
request object could be recycled before the associated response is
returned?
Not usually. So the thread hitting the NPE is something like
[catalina-exec- ...]?
And you are sure you aren't doing anything funny with the request
object? No storing it in the session or getting a reference from some
cross-thread storage mechanism?
Looking at the code
(https://github.com/apache/tomcat/blob/8.5.x/java/org/apache/catalina/connector/Request.java#L2465):
@Override
public StringBuffer getRequestURL() {
StringBuffer url = new StringBuffer();
String scheme = getScheme();
int port = getServerPort();
if (port < 0)
{
port = 80; // Work around java.net.URL bug
}
url.append(scheme);
url.append("://");
url.append(getServerName());
if ((scheme.equals("http") && (port != 80))
|| (scheme.equals("https") && (port != 443))) {
url.append(':'); // <<<< This is line 2465
url.append(port);
}
url.append(getRequestURI());
return url;
}
I don't see how an NPE could happen on that line.
But 8.5.69 was released on 2021-07-05, and that code is here:
https://github.com/apache/tomcat/blob/3e9dd49b20f9d6e270f8709d4f16d5595977595e/java/org/apache/catalina/connector/Request.java
This makes a little more sense:
@Override
public StringBuffer getRequestURL() {
StringBuffer url = new StringBuffer();
String scheme = getScheme();
int port = getServerPort();
if (port < 0)
{
port = 80; // Work around java.net.URL bug
}
url.append(scheme);
url.append("://");
url.append(getServerName());
if ((scheme.equals("http") && (port != 80)) // <<< This is 2465
|| (scheme.equals("https") && (port != 443))) {
url.append(':');
url.append(port);
}
url.append(getRequestURI());
return url;
}
So the scheme is null. That's odd, since getScheme is:
public String getScheme() {
return coyoteRequest.scheme().toString();
}
Oh, but coyoteRequest.scheme() returns a MessageBytes object whose
toString method can (somewhat surprisingly) return null(!) when the
type is T_NULL. And wouldn't you know it, here is the code for
MessageBytes.recycle() (which gets called whenever the request, and
therefore all of the various parts of the request, is recycled):
public void recycle() {
type=T_NULL;
byteC.recycle();
charC.recycle();
strValue=null;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
So it definitely looks like your request has been recycled somehow.
-chris
Chris, I was running 8.5.69. (I just bumped to 8.5.72). So I agree
that everything looks like a recycled request. This is a REST api.
Pretty much come in, do the work, return. The request object is
carried around throughout the api in a 'briefcase' object. But
nothing is cached in the session object or anywhere. Pretty much a new
sunrise on every api/servlet call coming in.
Just to confirm my understanding of request recycling, a request
object is assigned before the request is handed to my handler
servlet. When the servlet exits, the request object is recycled and
returned to the pool. For this problem to be a recycled request
problem, it would mean that I saved off a reference to a request and
referenced it on a different servlet call. It would have to be on a
different call, since the service of the api is a single thread in and
out. I've been around too long to say that it couldn't happen. But
at this point, I'm struggling with how. It would mean I would have to
discard the correct request for the servlet call and replace it with
an old version. I'm going to have add some logging statements. Is
there a unique id or something for each request object in the pool
that I might be able to get access to? If so, I can start logging the
request UID at the beginning of the servlet call and log the UID on
each access to the request object.
Let me know if there's some magic ID I can log to try to figure out
when i might be using a recycled instance?
Thx
Jerry
Problem solved. You were right about it being a recycled request
object. I had convinced myself the problem was occurring during one of
the direct api calls. When I traced back through the actual problem I
realized it was indeed occurring in one of the long-running threads. One
of these days, I'm going to learn the code I wrote... :-(.... Simple
fix. Thanks so much for pointing me in the right direction.
No problem.
To answer your earlier question about logging unique ids, I think you
could just do log.trace("Thread " + Thread.currentThread().getName() + "
is working with request " + request.hashCode()) and get what you are
looking for.
I don't believe Tomcat bothers to explicitly-assign unique identifiers
to its request objects, but as of this writing, the Request object
doesn't override Object.toString or Object.hashcode, so you end up
getting the equivalent of a unique-id for the object. You could
future-proof this by using System.identityHashCode(request) just in case
Tomcat changes its mind at some point.
If you disable request-reuse in Tomcat, you can protect yourself against
cross-request behavior in exchange for NPEs. If you think you may have
some mode bombs in your code, you might want to consider running in this
more -- at least in development -- until your are fairly confident you
aren't breaking the rules. If you have these kinds of bugs in your code,
you can cause some really surprising security problems.
-chris
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org
For additional commands, e-mail: users-h...@tomcat.apache.org