Google App Engine标准环境升级Java21后Jetty12闲置超时限制30秒
我在Google App Engine标准环境使用Java 8运行时多年,2024年初在其停止支持后成功迁移至Java 21运行时。升级后,当请求处理耗时超过30秒时,会收到如下500错误:
HTTP ERROR 500 java.util.concurrent.TimeoutException: Idle timeout expired: 30001/30000 ms
URI: http://XXXXX.appspot.com/servlet
STATUS: 500
MESSAGE: java.util.concurrent.TimeoutException: Idle timeout expired: 30001/30000 ms
CAUSED BY: java.util.concurrent.TimeoutException: Idle timeout expired: 30001/30000 ms
Caused by:
java.util.concurrent.TimeoutException: Idle timeout expired: 30001/30000 ms
at org.eclipse.jetty.io.IdleTimeout.checkIdleTimeout(IdleTimeout.java:167)
at org.eclipse.jetty.io.IdleTimeout.idleCheck(IdleTimeout.java:113)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
从Java 8迁移的优势之一是HTTP请求超时从1分钟延长到了10分钟,但这个30秒超时显然来自Jetty 12层。实际测试发现,处理流程能正常完成(耗时超1分钟),但错误并非在30秒后立即出现,而是在处理完成后才弹出——哪怕耗时5分钟,错误信息仍显示“30000 ms timeout”。重新加载页面就能看到处理完成后的结果。
我推测错误发生在Jetty层,但无法直接访问或修改该超时参数,且该错误未出现在Google Cloud的Log Explorer中。我尝试了以下配置均无效:
appengine-web.xml配置
<system-properties> <property name="appengine.api.urlfetch.defaultDeadline" value="10"/> <property name="org.eclipse.jetty.io.IdleTimeout._idleTimeout" value="50000"/> <property name="JAVA_USER_OPTS" value="-Dorg.eclipse.jetty.io.IdleTimeout._idleTimeout=50000"/> </system-properties> <env-variables> <env-var name="JETTY_PROPERTIES" value="-Dorg.eclipse.jetty.io.IdleTimeout._idleTimeout=50000"/> <env-var name="JETTY_PROPERTIES" value="-Djetty.http.idleTimeout=60000" /> <env-var name="JETTY_ARGS" value="idleTimeout=36000" /> <env-var name="JETTY_PROPERTIES" value="-Dserver.jetty.connection-idle-timout=36000" /> <env-var name="JETTY_ARGS" value="server.jetty.connection-idle-timout=36000" /> </env-variables>
web.xml配置
<context-param> <param-name>org.eclipse.jetty.server.IdleTimeout</param-name> <param-value>35000</param-value> </context-param>
pom.xml覆盖Jetty依赖
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-server --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>12.0.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-io --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-io</artifactId> <version>12.0.7</version> </dependency> <!-- https://mvnrepository.com/artifact/org.eclipse.jetty.ee10/jetty-ee10-servlet --> <dependency> <groupId>org.eclipse.jetty.ee10</groupId> <artifactId>jetty-ee10-servlet</artifactId> <version>12.0.6</version> </dependency>
我尝试通过Servlet获取Jetty服务器实例,代码如下:
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { java.util.Enumeration<String> attributeNames = req.getServletContext().getAttributeNames(); while (attributeNames.hasMoreElements()) { String attributeName = attributeNames.nextElement(); Object attributeValue = getServletContext().getAttribute(attributeName); log.warning(attributeName + ": " + attributeValue); if (attributeName.equals("org.eclipse.jetty.server.Executor")) { // Get all fields of the object Field[] fields = attributeValue.getClass().getDeclaredFields(); // Print the properties for (Field field : fields) { field.setAccessible(true); // Make the field accessible if it's private try { Object value = field.get(attributeValue); log.warning(field.getName() + ": " + value); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }
Log Explorer中的输出:
com.google.apphosting.runtime.jetty.APP_VERSION_CONTEXT_ATTR: com.google.apphosting.runtime.AppVersion@6f2b44ba org.eclipse.jetty.tlds: [jar:file:///workspace/WEB-INF/lib/org.glassfish.web.jakarta.servlet.jsp.jstl-3.0.1.jar!/META-INF/fmt.tld, org.eclipse.jetty.util.DecoratedObjectFactory: org.eclipse.jetty.util.DecoratedObjectFactory[decorators=2] org.eclipse.jetty.originAttribute: origin org.apache.tomcat.InstanceManager: org.apache.tomcat.SimpleInstanceManager@XXXXXX org.eclipse.jetty.quickstart.xml: /workspace/WEB-INF/quickstart-web.xml org.eclipse.jetty.server.Executor: QueuedThreadPool[qtp1105322512]@XXXXX{STARTED,0<=0<=100,i=0,r=-1,t=-46773ms,q=0}[ReservedThreadExecutor@XXXXXXX{reserved=0/1,pending=0}] jakarta.servlet.context.tempdir: /tmp/jetty-0_0_0_0-0-workspace-_-any- org.eclipse.jetty.ee10.quickstart.QuickStartDescriptorProcessor: org.eclipse.jetty.ee10.quickstart.QuickStartDescriptorProcessor@XXXXX org.apache.jasper.compiler.TldCache: org.apache.jasper.compiler.TldCache@XXXXX org.apache.jasper.runtime.JspApplicationContextImpl: org.apache.jasper.runtime.JspApplicationContextImpl@XXXXX
org.eclipse.jetty.server.Executor实例的属性:
LOG: org.slf4j.jul.JDK14LoggerAdapter@693f642d NOOP: org.eclipse.jetty.util.thread.QueuedThreadPool$$Lambda/0x00003e2948164ff8@5c84e324 _counts: 0|0 _evictThreshold: 4624122529 _threads: [] _joinLock: org.eclipse.jetty.util.thread.AutoLock$WithCondition@436a9f55 _jobs: [] _threadGroup: null _threadFactory: QueuedThreadPool[qtp1105322512]@41e1e210{STARTED,0<=0<=100,i=0,r=-1,t=-46777ms,q=0}[ReservedThreadExecutor@17bd3b7c{reserved=0/1,pending=0}] _name: qtp1105322512 **_idleTimeout: 60000** _maxThreads: 100 _minThreads: 0 _reservedThreads: -1 _tryExecutor: ReservedThreadExecutor@17bd3b7c{reserved=0/1,pending=0} _priority: 5 _daemon: false _detailedDump: false _lowThreadsThreshold: 1 _budget: org.eclipse.jetty.util.thread.ThreadPoolBudget@31ae5249 _stopTimeout: 5000 _virtualThreadsExecutor: null _maxEvictCount: 1 _runnable: org.eclipse.jetty.util.thread.QueuedThreadPool$Runner@772be7f0
可以看到_idleTimeout属性值为60000毫秒,猜测修改该值也无法解决问题。
内容的提问来源于stack exchange,提问作者Isaac Meany

