Skip to content

Commit f66b2fe

Browse files
Warmup java worker before specialization (#672)
* add warm up logics * update warm up request * minor updates * update logs * add warm up jar * add more logs for env testing * remove test logs * refractor code * add warm up log * add fix for java8 systemclassloader - add warmup funciton * revert pipeline change - add logs * restrucer warmup handler * stop system classloader load java library in warmup process * update warmup function * update log messages * adjust logs * fix warmup jar * refactor code * update warm up jar * style refactoring * Rename to worker warmup and added capability * Updating tests --------- Co-authored-by: Shreyas Gopalakrishna <shreyasg@microsoft.com>
1 parent 1483b11 commit f66b2fe

File tree

16 files changed

+276
-79
lines changed

16 files changed

+276
-79
lines changed

annotationLib/warmup-httptrigger.jar

4.09 KB
Binary file not shown.

src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ private void addHandlers() {
3838
JavaFunctionBroker broker = new JavaFunctionBroker(classPathProvider);
3939

4040
this.handlerSuppliers.put(StreamingMessage.ContentCase.WORKER_INIT_REQUEST, () -> new WorkerInitRequestHandler(broker));
41+
this.handlerSuppliers.put(StreamingMessage.ContentCase.WORKER_WARMUP_REQUEST, WorkerWarmupHandler::new);
4142
this.handlerSuppliers.put(StreamingMessage.ContentCase.FUNCTION_ENVIRONMENT_RELOAD_REQUEST, () -> new FunctionEnvironmentReloadRequestHandler(broker));
4243
this.handlerSuppliers.put(StreamingMessage.ContentCase.FUNCTION_LOAD_REQUEST, () -> new FunctionLoadRequestHandler(broker));
4344
this.handlerSuppliers.put(StreamingMessage.ContentCase.INVOCATION_REQUEST, () -> new InvocationRequestHandler(broker));

src/main/java/com/microsoft/azure/functions/worker/broker/CoreTypeResolver.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public static String getAnnotationName(Parameter parameter) {
4949
String annotationName = null;
5050

5151
for (Annotation annotation : annotations) {
52+
53+
//Checking if it's warmup function, warmup function has its own HttpTrigger class defined in the function jar file.
54+
//If it's not warmup function will bypass this check and fail back to normal logics.
55+
if (annotation.annotationType().getName().equals("com.microsoft.azure.functions.warmup.java.HttpTrigger")){
56+
annotationName = getBindingNameFromAnnotation(annotation);
57+
return annotationName;
58+
}
59+
5260
if (annotation.toString().contains("com.microsoft.azure.functions.annotation")) {
5361
annotationName = getBindingNameFromAnnotation(annotation);
5462
}

src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ private void addSearchPathsToClassLoader(FunctionMethodDescriptor function) thro
182182
if(function.getLibDirectory().isPresent()) {
183183
registerWithClassLoaderProvider(function.getLibDirectory().get());
184184
}else{
185-
registerJavaLibrary();
185+
registerJavaLibrary(function.isWarmup());
186186
}
187187
}
188188

@@ -194,9 +194,9 @@ void registerWithClassLoaderProvider(File libDirectory) {
194194
}
195195
}
196196

197-
void registerJavaLibrary(){
197+
void registerJavaLibrary(boolean isWarmup){
198198
try {
199-
if (!isTesting()){
199+
if (!isTesting() && !isWarmup){
200200
addJavaAnnotationLibrary();
201201
}
202202
} catch (Exception ex) {

src/main/java/com/microsoft/azure/functions/worker/description/FunctionMethodDescriptor.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,23 @@
66
import org.apache.commons.lang3.*;
77

88
public class FunctionMethodDescriptor {
9-
public FunctionMethodDescriptor(String id, String name, String fullMethodName, String jarPath) {
9+
private File parentDirectory;
10+
private File jarDirectory;
11+
private MethodInfo methodInfo;
12+
13+
private final String id;
14+
private final String jarPath;
15+
private final String name;
16+
private final String fullMethodName;
17+
private final boolean isWarmup;
18+
19+
public FunctionMethodDescriptor(String id, String name, String fullMethodName, String jarPath, boolean isWarmup) {
1020
this.id = id;
1121
this.name = name;
1222
this.fullMethodName = fullMethodName;
1323
this.methodInfo = new MethodInfo(fullMethodName);
1424
this.jarPath = StringUtils.trim(jarPath);
25+
this.isWarmup = isWarmup;
1526
}
1627

1728
/**
@@ -141,15 +152,10 @@ void guardAgainstUnqualifiedJarPath() {
141152
throw new IllegalArgumentException("\"" + jarPath + "\" is not a qualified JAR file name");
142153
}
143154
}
144-
145-
private File parentDirectory;
146-
private File jarDirectory;
147-
private MethodInfo methodInfo;
148-
149-
private final String id;
150-
private final String jarPath;
151-
private final String name;
152-
private final String fullMethodName;
155+
156+
public boolean isWarmup() {
157+
return isWarmup;
158+
}
153159

154160
/*
155161
* "struct" to track the info on the function method

src/main/java/com/microsoft/azure/functions/worker/handler/FunctionEnvironmentReloadRequestHandler.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,17 @@ public FunctionEnvironmentReloadRequestHandler(JavaFunctionBroker broker) {
2121
this.broker = broker;
2222
}
2323

24-
public Map<String, String> EnvironmentVariables = new HashMap<>();
24+
public Map<String, String> environmentVariables = new HashMap<>();
2525

2626
@Override
2727
String execute(FunctionEnvironmentReloadRequest request, Builder response) throws Exception {
2828
WorkerLogManager.getSystemLogger().log(Level.INFO, "FunctionEnvironmentReloadRequest received by the Java worker");
29-
EnvironmentVariables = request.getEnvironmentVariablesMap();
30-
if (EnvironmentVariables == null || EnvironmentVariables.isEmpty()) {
31-
return String
32-
.format("Ignoring FunctionEnvironmentReloadRequest as newSettings map is either empty or null");
29+
environmentVariables = request.getEnvironmentVariablesMap();
30+
if (environmentVariables.isEmpty()) {
31+
return "Ignoring FunctionEnvironmentReloadRequest as newSettings map is empty.";
3332
}
34-
setEnv(EnvironmentVariables);
35-
return String.format("FunctionEnvironmentReloadRequest completed");
33+
setEnv(environmentVariables);
34+
return "FunctionEnvironmentReloadRequest completed";
3635
}
3736

3837
/*

src/main/java/com/microsoft/azure/functions/worker/handler/FunctionLoadRequestHandler.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,28 @@
1111

1212

1313
public class FunctionLoadRequestHandler extends MessageHandler<FunctionLoadRequest, FunctionLoadResponse.Builder> {
14+
15+
private final JavaFunctionBroker broker;
16+
private boolean isWarmup;
17+
1418
public FunctionLoadRequestHandler(JavaFunctionBroker broker) {
1519
super(StreamingMessage::getFunctionLoadRequest,
1620
FunctionLoadResponse::newBuilder,
1721
FunctionLoadResponse.Builder::setResult,
1822
StreamingMessage.Builder::setFunctionLoadResponse);
19-
2023
this.broker = broker;
2124
}
2225

26+
public FunctionLoadRequestHandler(JavaFunctionBroker broker, boolean isWarmup){
27+
this(broker);
28+
this.isWarmup = isWarmup;
29+
}
30+
2331
@Override
2432
String execute(FunctionLoadRequest request, FunctionLoadResponse.Builder response) throws Exception {
2533
WorkerLogManager.getSystemLogger().log(Level.INFO, "FunctionLoadRequest received by the Java worker, Java version - " + Util.getJavaVersion());
2634
final RpcFunctionMetadata metadata = request.getMetadata();
27-
final FunctionMethodDescriptor descriptor = createFunctionDescriptor(request.getFunctionId(), metadata);
35+
final FunctionMethodDescriptor descriptor = createFunctionDescriptor(request.getFunctionId(), metadata, this.isWarmup);
2836

2937
final Map<String, BindingInfo> bindings = metadata.getBindingsMap();
3038

@@ -38,9 +46,8 @@ String execute(FunctionLoadRequest request, FunctionLoadResponse.Builder respons
3846
descriptor.getFullMethodName());
3947
}
4048

41-
FunctionMethodDescriptor createFunctionDescriptor(String functionId, RpcFunctionMetadata metadata) {
42-
return new FunctionMethodDescriptor(functionId, metadata.getName(), metadata.getEntryPoint(), metadata.getScriptFile());
49+
FunctionMethodDescriptor createFunctionDescriptor(String functionId, RpcFunctionMetadata metadata, boolean isWarmup) {
50+
return new FunctionMethodDescriptor(functionId, metadata.getName(), metadata.getEntryPoint(), metadata.getScriptFile(), isWarmup);
4351
}
4452

45-
private final JavaFunctionBroker broker;
4653
}

src/main/java/com/microsoft/azure/functions/worker/handler/RpcLogHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public RpcLogHandler(LogRecord record, String invocationId) {
1919
private static RpcLog.Builder generateRpcLog(LogRecord record, String invocationId) {
2020
RpcLog.Builder log = RpcLog.newBuilder();
2121
/**
22-
* Check if the logging namespace belongs to system logsq, invocation log should be categorized to user type (default), others should
22+
* Check if the logging namespace belongs to system logs, invocation log should be categorized to user type (default), others should
2323
* be categorized to system type.
2424
*
2525
* local_console customer_app_insight functions_kusto_table

src/main/java/com/microsoft/azure/functions/worker/handler/WorkerInitRequestHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ String execute(WorkerInitRequest request, WorkerInitResponse.Builder response) {
2525
response.putCapabilities("RpcHttpBodyOnly", "RpcHttpBodyOnly");
2626
response.putCapabilities("RpcHttpTriggerMetadataRemoved", "RpcHttpTriggerMetadataRemoved");
2727
response.putCapabilities("HandlesWorkerTerminateMessage", "HandlesWorkerTerminateMessage");
28+
response.putCapabilities("HandlesWorkerWarmupMessage", "HandlesWorkerWarmupMessage");
2829
response.setWorkerMetadata(composeWorkerMetadata());
2930
return "Worker initialized";
3031
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.microsoft.azure.functions.worker.handler;
2+
3+
import com.microsoft.azure.functions.rpc.messages.*;
4+
import com.microsoft.azure.functions.worker.WorkerLogManager;
5+
import com.microsoft.azure.functions.worker.broker.JavaFunctionBroker;
6+
import com.microsoft.azure.functions.worker.reflect.FactoryClassLoader;
7+
8+
import java.util.*;
9+
10+
import static com.microsoft.azure.functions.worker.Constants.JAVA_LIBRARY_DIRECTORY;
11+
12+
13+
public class WorkerWarmupHandler extends MessageHandler<WorkerWarmupRequest, WorkerWarmupResponse.Builder> {
14+
15+
private static final String WARM_UP_FUNCTION_NAME = "WarmupFunc";
16+
private static final String WARM_UP_FUNCTION_ENTRY_POINT = "com.microsoft.azure.functions.warmup.java.Function.run";
17+
private static final String WARM_UP_FUNCTION_SCRIPT_FILE = JAVA_LIBRARY_DIRECTORY + "/warmup-httptrigger.jar";
18+
private final JavaFunctionBroker javaFunctionBroker = new JavaFunctionBroker(new FactoryClassLoader().createClassLoaderProvider());
19+
20+
public WorkerWarmupHandler() {
21+
super(StreamingMessage::getWorkerWarmupRequest,
22+
WorkerWarmupResponse::newBuilder,
23+
WorkerWarmupResponse.Builder::setResult,
24+
StreamingMessage.Builder::setWorkerWarmupResponse);
25+
}
26+
27+
@Override
28+
String execute(WorkerWarmupRequest workerWarmupRequest, WorkerWarmupResponse.Builder builder) {
29+
try {
30+
WorkerLogManager.getSystemLogger().info("azure function java worker warm up start.");
31+
this.javaFunctionBroker.setWorkerDirectory(workerWarmupRequest.getWorkerDirectory());
32+
warmupFunctionEnvironmentReload();
33+
UUID functionId = warmupFunctionLoad(workerWarmupRequest);
34+
warmupInvocation(functionId);
35+
WorkerLogManager.getSystemLogger().info("azure function java worker warm up completed successfully.");
36+
} catch (Exception e) {
37+
WorkerLogManager.getSystemLogger().severe("warm up process failed with exception: " + e.getMessage());
38+
throw new RuntimeException(e);
39+
}
40+
return "azure function java worker warm up completed";
41+
}
42+
43+
private void warmupFunctionEnvironmentReload() throws Exception {
44+
FunctionEnvironmentReloadRequest functionEnvironmentReloadRequest = FunctionEnvironmentReloadRequest.newBuilder()
45+
.putAllEnvironmentVariables(System.getenv())
46+
.build();
47+
new FunctionEnvironmentReloadRequestHandler(this.javaFunctionBroker).execute(functionEnvironmentReloadRequest, null);
48+
WorkerLogManager.getSystemLogger().info("finish warm up FunctionEnvironmentReloadRequestHandler");
49+
}
50+
51+
private UUID warmupFunctionLoad(WorkerWarmupRequest workerWarmupRequest) throws Exception {
52+
Map<String, BindingInfo> map = new HashMap<>();
53+
BindingInfo httpTrigger = BindingInfo.newBuilder().setDirection(BindingInfo.Direction.in).setDataType(BindingInfo.DataType.undefined).setType("httpTrigger").build();
54+
map.put("req", httpTrigger);
55+
BindingInfo http = BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).setDataType(BindingInfo.DataType.undefined).setType("http").build();
56+
map.put("$return", http);
57+
RpcFunctionMetadata rpcFunctionMetadata = RpcFunctionMetadata.newBuilder()
58+
.setName(WARM_UP_FUNCTION_NAME)
59+
.setEntryPoint(WARM_UP_FUNCTION_ENTRY_POINT)
60+
.setScriptFile(workerWarmupRequest.getWorkerDirectory() + WARM_UP_FUNCTION_SCRIPT_FILE)
61+
.putAllBindings(map)
62+
.build();
63+
final UUID functionId = UUID.randomUUID();
64+
FunctionLoadRequest functionLoadRequest = FunctionLoadRequest.newBuilder()
65+
.setFunctionId(functionId.toString())
66+
.setMetadata(rpcFunctionMetadata)
67+
.build();
68+
String loadRequestResult = new FunctionLoadRequestHandler(this.javaFunctionBroker, true).execute(functionLoadRequest, FunctionLoadResponse.newBuilder());
69+
WorkerLogManager.getSystemLogger().info("finish warm up FunctionLoadRequestHandler with result: " + loadRequestResult);
70+
return functionId;
71+
}
72+
73+
private void warmupInvocation(UUID functionId) throws Exception {
74+
List<ParameterBinding> inputDataList = new ArrayList<>();
75+
ParameterBinding parameterBinding = ParameterBinding.newBuilder()
76+
.setName("req")
77+
.setData(TypedData.newBuilder().setHttp(RpcHttp.newBuilder().setMethod("GET")))
78+
.build();
79+
inputDataList.add(parameterBinding);
80+
InvocationRequest invocationRequest = InvocationRequest.newBuilder()
81+
.setFunctionId(functionId.toString())
82+
.setInvocationId(UUID.randomUUID().toString())
83+
.addAllInputData(inputDataList)
84+
.build();
85+
String invocationResult = new InvocationRequestHandler(this.javaFunctionBroker).execute(invocationRequest, InvocationResponse.newBuilder());
86+
WorkerLogManager.getSystemLogger().info("finish warm up InvocationRequestHandler with result: " + invocationResult);
87+
}
88+
}

src/main/java/com/microsoft/azure/functions/worker/reflect/EnhancedClassLoaderProvider.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.microsoft.azure.functions.worker.*;
1010

1111
public class EnhancedClassLoaderProvider implements ClassLoaderProvider {
12+
1213
public EnhancedClassLoaderProvider() {
1314
customerUrls = Collections.newSetFromMap(new ConcurrentHashMap<URL, Boolean>());
1415
workerUrls = Collections.newSetFromMap(new ConcurrentHashMap<URL, Boolean>());
@@ -51,8 +52,9 @@ public void addWorkerUrl(URL url) throws IOException {
5152
WorkerLogManager.getSystemLogger().info("Loading worker file URL: " + url);
5253
workerUrls.add(url);
5354
}
55+
5456
private final Set<URL> customerUrls;
5557
private final Set<URL> workerUrls;
5658
private final Object lock = new Object();
57-
private static volatile URLClassLoader classLoaderInstance;
59+
private volatile URLClassLoader classLoaderInstance;
5860
}

0 commit comments

Comments
 (0)