Octane memory leak risks0:00
In this lesson, we are going to review the guidelines we need to follow to ensure our applications and packages work nicely with Octane. The main issues that we need to avoid are number one, storing stuff in memory and not clearing it. This causes what's called a memory leak. And a memory leak is when we put data in a computer memory and not remove it when it's no longer needed. It would or this would increase the memory allocated to our program until all server memory is consumed. For issue number two, it is leaking state between requests when that's not supposed.
Global variable example0:37
memory is consumed. For issue number two, it is leaking state between requests when that's not supposed to happen. This may have very risky side effects that can be considered equivalent to security issues. We want to avoid that at all costs. So let's jump into the code and see what we can do to avoid these issues. In this example, we have a variable called values. This variable holds an empty array. The variable is in the global scope, which means it's stored in the php process memory. And as long as the process is running, the variable will remain.
The variable is in the global scope, which means it's stored in the PHP process memory. And as long as the process is running, the variable will remain. Inside a route that matches the global endpoint, the route action inherits the values variable from the global scope. That way, we can access this variable from inside the closure. We also inherit this variable by reference. You can see we are using an ampersand before the variable name. So the variable is inherited by reference. With this setup, code inside the closure has access to read and write to the values global variable.
With this setup, code inside the closure has access to read and write to the values global variable. Inside the closure, we push a random number into the array and return the size of the array as a response. In regular PHP FPM, the response from this endpoint will always be one, as the size of the array will always be one. This is because the global context is reset between requests. Now let's see what happens when we use Octane. So let's go to the browser and visit the global endpoint. We can see that the response is one, as the size of the array is one.
State persists in Octane2:25
So let's go to the browser and visit the global endpoint. We can see that the response is one, as the size of the array is one. Let's now refresh the browser to send another request, and the response is now two. And if we keep refreshing, the counter keeps going up, meaning that the size of the array is growing as we push more elements into it on every request. If our endpoint is under high traffic, the array will keep growing until it eats all available memory, and then the Octane process will crash. That's why in the shared memory model, we need to be super careful around variables in the global scope. And this also applies to static properties of an object, since static properties also
Clearing static/global state3:15
in the global scope. And this also applies to static properties of an object, since static properties also live in the global scope and won't get destructed when the object is destructed. If you have to introduce a static property in your code, make sure you flush the values after every request. You can do that by listening to the request terminated event in the Octane configuration file, or by listening to the request received event and reset the scope or reset the context before handling every request. As for global variables, it is recommended that you avoid using them completely, as resetting these variables can be tricky.
Container binding vs singleton3:51
As for global variables, it is recommended that you avoid using them completely, as resetting these variables can be tricky. Now let's go check a similar issue that can be introduced while using the Laravel container. I'm here inside the AppServiceProvider, and I've registered a service called Store. This service returns an instance of a class called Store. Let's check it out. The class has one property and two methods. The values property is an array, and the store method adds a new value to this array. The other method is called size, which returns the size of the values array. Now let's go to our web.php routes file, and update the code to use the Store container service.
The other method is called size, which returns the size of the values array. Now let's go to our web.php routes file, and update the code to use the StoreContainer service. Let's remove the global variable, remove the use statement, resolve the store service from the container, and store it in a store variable, store equals AppStore. Add a value to the store, let's add the current time, and then return the size of the store, store size. Now let's start the Octane server, and go visit the global endpoint. And the response is 1, no matter how many times we refresh. If we go check the AppServiceProvider class, we can see that the service is registered as a regular binding, not a singleton, meaning that a new instance will be created each time.
If we go check the AppServiceProvider class, we can see that the service is registered as a regular binding, not a singleton, meaning that a new instance will be created each time we request the service. Now let's switch the binding to a singleton, and then we go restart Octane, visit the endpoint in the browser, and same, the response is 1, no matter how many times we refresh. And that's because Octane clones a new container instance for every request. So a singleton only lives inside the request container instance and is destructed after the request is handled. New requests get a fresh instance of the singleton. Now let's try to resolve the store service inside the boot method, this method is called
Singleton shared across requests6:21
New requests get a fresh instance of the singleton. Now let's try to resolve the store service inside the boot method, this method is called when the original container instance is created, so the singleton will live inside that instance and will be shared between requests. Now let's go restart Octane, then we go check the browser, the response is 1, but if we refresh multiple times, you can see that the number increments. Now you can tell that the store singleton is being reused between requests, any values we store inside the store will remain there until we manually clear it. If we don't, our memory will be consumed and the Octane process will crash. Let's now try to remove this from the boot method, and go to the Octane.php file, and
If we don't, our memory will be consumed and the Octane process will crash. Let's now try to remove this from the boot method, and go to the Octane.php file, and register the store service to be worn by Octane. Let's restart the Octane server, now if we check the browser, and refresh multiple times, the counter increments, over and over and over, again that's because the store service is worn by Octane in the original container instance and will be shared between requests. So when using singletons, make sure your application is designed to handle sharing this singleton between requests, otherwise, just make sure the singleton isn't resolved while the application is booting up and is not worn by Octane, that way, the singleton will just live for the duration of a request and then will get flushed out at the end.
is booting up and is not worn by Octane, that way, the singleton will just live for the duration of a request and then will get flushed out at the end. By keeping both these concerns in mind, you will avoid memory leaks in your application, and will also avoid leaking the state between requests when this is not supposed to happen. You have to be mindful about what's shared and what's not. That's the end of this lesson, and I will see you in the next one.
