Originally published on Medium.com
Welcome to my third article in my AWS Lambda series. In my previous article we talked about the right amount of memory and whether or not more memory grants you more cores. Today I am looking into how state management with variables works and what happens if we run Java code in AWS Lambda.
If you are a Java Developer and working with AWS Lamdba then this article is for you!
tl;dr: There will be singletons!
State management in Java programs
How do we keep state in Java programs? Exactly! We use variables! Variables in Java have different scopes and lifetimes:
- Instance Variables (Non-Static Fields)
- Class Variables (Static Fields)
- Local Variables
- Parameters
In Java programs, we usually pass objects around. Objects store data in class or instance variables. Therefore state management is all about variables. Variables are the basic building block of data. Let’s recap!
Setting up the stage
Here is a Java class already prepared for running as a AWS Lambda function. Comprehend what the following code does and focus on the declared variables: classloaderTime, constructorTime and methodTime. Each variable gets initialized with a timestamp, but during different instants at runtime.
We can print the value of each variable if we call the method handleRequest, which is also the entry point for your AWS Lambda function.
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class LambdaFunction implements RequestHandler<Void, String> {
private static final long classloaderTime;
private final long constructorTime;
private long methodTime;
static {
classloaderTime = System.currentTimeMillis();
}
public LambdaFunction() {
constructorTime = System.currentTimeMillis();
}
public String handleRequest(Void args, Context context) {
methodTime = System.currentTimeMillis();
return new StringBuilder()
.append("classloaderTime=")
.append(classloaderTime)
.append(", constructorTime=")
.append(constructorTime)
.append(", methodTime=")
.append(methodTime)
.toString();
}
}
Let’s say this is a piece of code that would run in a regular application of yours. Say on in a JVM on your local machine. What would happen? What would you expect?
- classloaderTime is a class variable
- constructorTime is an instance variable
- methodTime is a local variable
We know that each one has a different scope:
- classloaderTime is initialized once the class is loaded
- constructorTime is initialized every time we create a new object
- methodTime is initialized every time we call the method
How do the variables retain state during the lifetime of a typical Java program?
How variables in Java programs work
Let’s write some method to find out! It is not hard to follow, even though Medium lacks syntax highlighting.
public static void main(String[] args) {
LambdaFunction function = new LambdaFunction();
System.out.println(function.handleRequest(null, null));
sleep();
System.out.println(function.handleRequest(null, null));
sleep();
System.out.println(function.handleRequest(null, null));
sleep();
function = new LambdaFunction();
System.out.println(function.handleRequest(null, null));
sleep();
System.out.println(function.handleRequest(null, null));
sleep();
System.out.println(function.handleRequest(null, null));
}
(The method sleep pauses the current thread for 25ms.)
First, we create a new object of the class LambdaFunction. Then we invoke it three times. After that, we create another new object and invoke it again three times.
Here is the output showing the values of the variables:
- classloaderTime=1565858740969, constructorTime=1565858740969, methodTime=1565858740969
- classloaderTime=1565858740969, constructorTime=1565858740969, methodTime=1565858740997
- classloaderTime=1565858740969, constructorTime=1565858740969, methodTime=1565858741022
- classloaderTime=1565858740969, constructorTime=1565858741050, methodTime=1565858741050
- classloaderTime=1565858740969, constructorTime=1565858741050, methodTime=1565858741077
- classloaderTime=1565858740969, constructorTime=1565858741050, methodTime=1565858741103
The variable classloaderTime keeps the value across several method calls. The value remains the same. The variable constructorTime is an instance variable and we can see a new value once we create a new object. The variable methodTime has always a new value.
I don’t think this surprises you much. This only proves the assumptions we have about Java’s variables.
- classloaderTime is a class variable and is initialized once the class is loaded
- constructorTime is an instance variable and is initialized every time we create a new object
- methodTime is a local variable and is initialized every time we call the method
Alright, now let us see what happens if we run this code in AWS Lambda.
How variables in Java programs inside AWS Lambda work
Next, we set up this Java code in AWS Lambda and run it. This time not with a main method, but by invoking the execution context.
AWS Lambda creates an execution context for the code and reuses it upon following invocations of said function. If the execution context is idle for too long it gets discarded.
You as a Java Developer need to remember, that you have absolutely zero control over execution context creation, reuse, and destruction. Zero! There might be one execution context or several. On which one your code runs, you do not know. Your Lambda function might start cold or warm. Your code does not even know about the existence of an execution context.
How does this affect our variables? We check! Invoke the Lambda function several times and examine the output.
Here is mine:
- classloaderTime=1565860728315, constructorTime=1565860728322, methodTime=1565860761729
- classloaderTime=1565860728315, constructorTime=1565860728322, methodTime=1565860777845
- classloaderTime=1565860728315, constructorTime=1565860728322, methodTime=1565860784292
Did you expect that? Well, I definitly did not! 😮
Local variables behave pretty much like we expect. They get initialized every time we call the method. They do not retain their value inbetween method invocations. The local variable methodTime has a new value every time we invoke the Lambda function. This is no different from a regular Java program.
Things are a bit different for class and instance variables. classloaderTime and constructorTime retain their value inbetween invocation!
I did not expect this. I thought we are getting a new object every time we call the Lambda function, or a new JVM, or something, but not the same object!
One can only suppose that:
- The execution context does not create a new object every time it is called.
- Class and instance variables are not distinguishable during runtime
- The execution context keeps a singleton of your object around
This only holds true if the execution context is reused! Grab a coffee, talk to you coworkers and after some time invoke the Lambda function again. You will see that now classloaderTime has a different value.
Conclusion
Class or instance variables do not matter for you anymore. The execution context runs your code in a singleton. If there is more than once execution context, each one will run a singleton of your code. You can not share state among them. They are independent from each other.
You don’t get a new object if your execution context is still warm. The only time you get a new object is if the execution context is freshly created. When and how often this happens is out of your control.
Protip: better not rely on retaining state at all and just use instance variables. Might save you some issues with concurrency.