PHP

Session locking – big bad and sometimes (or mostly) unnoticed until it’s too late con of long polling


In this post I’m not going to discuss long polling VS short polling VS sockets, I’m also not going to say anything against (or in favor) of long polling. I assume that anyone reading this already done their research and considerations and aware of most (or all) of the pros and the cons of each method. I just wanted to share something that is not mentioned in most of the discussions i heard about the polling methods: PHP’s sessions locks.

By default, PHP uses files for storing session data. This means that in case of long polling, by default, the relevant session file will be locked, so any additional incoming request from the same user will have to wait until the session file will be unlocked.

“So what? This is a price I’m willing to pay”

Follow me on this one – you have a client app that creates a long polling connection, waiting for the response, even displaying a nice loading screen. So far a good (or ok-eish) user experience. But what if the user will say “Ok then, i know this is going to take a while. So i’ll just open a new tab while this is loading and do other stuff”? Well, if you were not prepared for this, then you are, gently put, screwed. The user won’t be able to access your application, and I’m not talking about some nice ‘Please wait, in process’ screen, I’m talking about server-takes-forever-to-respond scenario.

I’m probably a little bit over-dramatic here. But this will happen, and if it isn’t ok with you (or your product manager), than it will be a problem that may be a bit expensive to fix, depending on when you discover it.

“On second thought, maybe it’s a price I’m not willing to pay”

You can take different approaches on fixing this, depending on how much you are willing to invest (in time or money), the stage your project at, your infrastructure and other factors.

  • Explicitly closing the session with session_write_close – Simple in theory: After finishing writing data to the session file, you can close it and unblock it for other processes, afterwards you’ll be able to read from the session but not write to it. In practice you probably should take more cautious approach with this one, most backend servers today written on top of frameworks, whether its Laravel, Yii, Cake or anything else. Each of those frameworks ships with components that require session writing privileges, components such as Authentication, Permissions etc’. So make sure you really understand what’s going under the hood of your framework before unblocking the session.
  • Using non-blocking session storage such as a database, redis, memcached or other equivalents – Some may argue, but this is my personal favorite for couple of reasons:

    1. Managing session locks by yourself is more prone to mistakes than letting a fully tested and proven framework to do it for you.
    2. It prevents a possible security breach where session file stored in a shared folder and may be accessible to unauthorized 3rd parties.
    3. It’s easier to move to a multi-server setup when your session data is accessible from multiple servers.
    4. Performance wise you have a larger set of tools to optimize your database speed than you have with a filesystem.

To summarize, long polling is still a valid approach in many use cases, just make sure you (and/or your product team) understand the full list of pros and cons of doing it.

Laravel, PHP

Exception in the exceptions handler


I’ve stumbled upon this issue couple of months ago, when our Laravel based backend server stopped logging and handling exception that was thrown in a specific flow that included a file upload.

The easy part was to find this exception, a quick peek in Apache’s logs revealed what was hiding: FileNotFoundException. But the file existed, the upload was successful and i had no idea which file Willis was talking ’bout. After a while i found the real exception, some edge case data integrity issue. The small problem was fixed quickly, but the bigger problem just began; there is a case where our exception handler fails for unknown (yet) reason.

To make a long story short – there was a problem (or intentional design) in httpFoundation\File implementation that didn’t played nicely with our attempt to capture the request URL which caused the exception and log it. So eventually –

Request::capture()->url();

In our exception handler caused another exception. And there you go, Exception in the exceptions handler.

Handling exceptions in the exception handler

As you may know, Laravel’s exception handler extends ExceptionHandler and have 2 methods:

  • report – that is responsible for reporting the exception, there you may log it, send it to external bug management tools such as New Relic or anything else.
  • render – that is responsible for creating the HTTP response that will be sent back to the browser.

After report method finished processing whatever you defined as a proper exception handling, it will pass the exception to it’s parent (ExceptionHandler) report method, unless of course you had a problem in the handler.

So how would you handle an exception in the handler itself?

A simple try-catch-finally block was the solution for this exception-seption.
Consider the following modification of Laravels report method:

public function report(Exception $e) {
	
	try {
	
		// Try to execute exception handler
		Log::error("Exception ... ");
		
		// ... Report to New Relic
		// ... Blame the engineers
	
	} catch(Exception $handlerException) {
	
		// Report also the handler's Exception
		parent::report($handlerException);
	
	} finally {
	
		// Whether the try part succeeded or not, report the original exception
		return parent::report($e);
	}
}

The logic is simple. try to execute your handler, catch if something goes wrong and report it, and then finally report the original exception that caused the exception that activated the exception handler. In this case, if god forbid, your handler will break, nothing will go unnoticed, not the original exception, and not the exception in the handler.