Skip to content

Commit a3d23cc

Browse files
committed
Set the Thread Context ClassLoader while executing function code.
Users justifiably expect that the TCCL will be the loader of their code, and lots of things fail to work if that is not true. PiperOrigin-RevId: 301824288
1 parent 9f350c6 commit a3d23cc

File tree

3 files changed

+26
-6
lines changed

3 files changed

+26
-6
lines changed

invoker/core/src/main/java/com/google/cloud/functions/invoker/NewBackgroundFunctionExecutor.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,18 @@ private static Map<String, Object> httpHeaderMap(HttpServletRequest req) {
168168
* unmarshalling this event, if it is a CloudEvent.
169169
*/
170170
private abstract static class FunctionExecutor<CloudEventDataT> {
171-
private final String functionName;
171+
private final Class<?> functionClass;
172172

173-
FunctionExecutor(String functionName) {
174-
this.functionName = functionName;
173+
FunctionExecutor(Class<?> functionClass) {
174+
this.functionClass = functionClass;
175175
}
176176

177177
final String functionName() {
178-
return functionName;
178+
return functionClass.getCanonicalName();
179+
}
180+
181+
final ClassLoader functionClassLoader() {
182+
return functionClass.getClassLoader();
179183
}
180184

181185
abstract void serviceLegacyEvent(HttpServletRequest req)
@@ -193,7 +197,7 @@ private static class RawFunctionExecutor extends FunctionExecutor<Map<?, ?>> {
193197
private final RawBackgroundFunction function;
194198

195199
RawFunctionExecutor(RawBackgroundFunction function) {
196-
super(function.getClass().getCanonicalName());
200+
super(function.getClass());
197201
this.function = function;
198202
}
199203

@@ -234,7 +238,7 @@ private static class TypedFunctionExecutor<T> extends FunctionExecutor<T> {
234238
private final BackgroundFunction<T> function;
235239

236240
private TypedFunctionExecutor(Type type, BackgroundFunction<T> function) {
237-
super(function.getClass().getCanonicalName());
241+
super(function.getClass());
238242
this.type = type;
239243
this.function = function;
240244
}
@@ -287,7 +291,9 @@ Class<T> cloudEventDataType() {
287291
@Override
288292
public void service(HttpServletRequest req, HttpServletResponse res) throws IOException {
289293
String contentType = req.getContentType();
294+
ClassLoader oldContextLoader = Thread.currentThread().getContextClassLoader();
290295
try {
296+
Thread.currentThread().setContextClassLoader(functionExecutor.functionClassLoader());
291297
if (contentType != null && contentType.startsWith("application/cloudevents+json")) {
292298
serviceCloudEvent(req, CloudEventKind.STRUCTURED);
293299
} else if (req.getHeader("ce-specversion") != null) {
@@ -299,6 +305,8 @@ public void service(HttpServletRequest req, HttpServletResponse res) throws IOEx
299305
} catch (Throwable t) {
300306
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
301307
logger.log(Level.WARNING, "Failed to execute " + functionExecutor.functionName(), t);
308+
} finally {
309+
Thread.currentThread().setContextClassLoader(oldContextLoader);
302310
}
303311
}
304312

invoker/core/src/main/java/com/google/cloud/functions/invoker/NewHttpFunctionExecutor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ public static NewHttpFunctionExecutor forClass(Class<?> functionClass) {
6161
public void service(HttpServletRequest req, HttpServletResponse res) {
6262
HttpRequestImpl reqImpl = new HttpRequestImpl(req);
6363
HttpResponseImpl respImpl = new HttpResponseImpl(res);
64+
ClassLoader oldContextLoader = Thread.currentThread().getContextClassLoader();
6465
try {
66+
Thread.currentThread().setContextClassLoader(function.getClass().getClassLoader());
6567
function.service(reqImpl, respImpl);
6668
} catch (Throwable t) {
6769
// TODO(b/146510646): this should be logged properly as an exception, but that currently
@@ -71,6 +73,7 @@ public void service(HttpServletRequest req, HttpServletResponse res) {
7173
t.printStackTrace();
7274
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
7375
} finally {
76+
Thread.currentThread().setContextClassLoader(oldContextLoader);
7477
try {
7578
// We can't use HttpServletResponse.flushBuffer() because we wrap the PrintWriter
7679
// returned by HttpServletResponse in our own BufferedWriter to match our API.

invoker/testfunction/src/test/java/com/example/functionjar/Checker.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@
1818

1919
class Checker {
2020
void serviceOrAssert(String runtimeClassName) {
21+
// Check that the context class loader is the loader that loaded this class.
22+
if (getClass().getClassLoader() != Thread.currentThread().getContextClassLoader()) {
23+
throw new AssertionError(
24+
String.format(
25+
"ClassLoader mismatch: mine %s; context %s",
26+
getClass().getClassLoader(),
27+
Thread.currentThread().getContextClassLoader()));
28+
}
29+
2130
ClassLoader myLoader = getClass().getClassLoader();
2231
Class<Template> templateClass = Template.class;
2332
ClassLoader templateLoader = templateClass.getClassLoader();

0 commit comments

Comments
 (0)