 Okay, so unfortunately, one of our speakers had to cancel and normally that would have been told by Tosco Pruba that he's ill and he had to cancel, so now we will have Holger Waltersdorf instead, who will talk to us about the ACMPHP requests. Holger is also a member of the PHP user group in Dresden, or the founder of the PHP. And he also organizes the PHP DevDays in Dresden, so please go ahead. Thank you very much. Yeah, as you said, my name is Holger. I'm working for Fortune Globe. We are doing e-commerce for the fashion industry. You can find me on Twitter and on GitHub and on several other places. As he mentioned, I'm running the PHP user group, or at least I'm one of the runners of the PHP user group. So, and today I want to talk about PHP and asynchronous requests. And PHP and async always was kind of love-hate relationship, because asynchronous requests are not built into PHP, so not really. So, to demonstrate the whole thing, I chose a very simple use case. Let's consider we have a PHP creation, PDF creation service, which basically on management level looks like this. So, user sends a request to the web service and the web service creates three PDF files. So, if we have only three PDF files, we can produce them sequentially. Which is kind of okay, because it's not consuming too much time. But what if we have a lot of PDFs that we have to create at the same time? If we do that sequentially, so we have to create nine PDFs now, that probably will take a lot of time. And it's maybe not fast enough for the user and for your web service and everything. So, what we are basically doing or need is we need to parallelize those requests or those creation of the PDF. Yeah, to make it faster for the end user, because we are doing multiple jobs at the same time. So, let's look back. How did we try to do asynchronous requests in PHP in the past? So, I have some code snippets. Please take them funny, because they are. Yeah, we did something like that, right? Create PDF, def no, throw it away. You can do the same thing with shell exec or proc open. It's basically all the same thing. That works since PHP 4. Yeah, so even if you run very old PHP, you can do that. So, that works, of course, but we have a lot of downsides here. First, we need to call the script in CLI mode, which is a completely different environment than our web service. If we have a long command with a lot of options for the PDF, that command gets quite messy. Yeah, Dartlin handling is based on the argv from the CLI. And we don't get any response back, because we are sending everything to DevNet. Yeah, try that on debugging, especially in production. So, let's do some HTTP, because everybody can HTTP, right? So, yeah. Next thing is we are doing curl request, same script. And the clue is here, the time of millisecond one. That makes it really send asynchronously. So, the curl is coming back here and closes after, so the script is running and the curl resource is closed. So, basically, it's the same behavior. We are sending everything to DevNet, because we don't get any response back. So, the problem here is we have a web server involved because we are sending an actual HTTP request. The web server can be overhead, of course, because HTTP has some overhead and can also be an error source when it comes to a debugging. So, maybe even a local answer is involved. So, you have another system in front of that. So, you have two environments that you have to maintain, at least. You need the curl extension in PHP. Again, the response is thrown away and the called script must be exposed under the document root. So, that could be also a security issue, depending on the job that you are doing in the background. In this case, when you want to call it over in URL, you have to expose it to the web server. So, we also tried very hard doing as many stuff by just building up queues, right? So, just put that script in the queue and then create a Chrome tab that is running every minute and is running those scripts. So, of course, also that works, but you don't have on-demand execution. So, it's not suitable for a web service, of course. It needs a lot of logging and logging because you have concurrency and you need to show what is happening in the background. You easily have race conditions and you also can have... If one of those jobs is going wild, you're kind of fun because, like I said, it's running every minute. So, your server is maybe soon very dead. And you also have your database load because you're using technically elusive data. Which is a bad idea, of course. And you have to maintain something outside of your PHP project, the Chrome tab. So, you have to go or have to access some DevOps material. And, of course, this is hard to test in the scenario of creating multiple jobs at the same time. So, yeah, we also tried to be clever doing something like this. Register a shutdown function. Doing a redirect and the shutdown function is running after the redirect. Also works. But, again, there's a web server involved. We have no response. If something is running in the shutdown function, you will have memory leaks. And you cannot throw exceptions in the shutdown function because there is no exception handler anymore, which also is a pretty bad idea. Yeah, and you have no execution time limit because you are already in the shutdown function. So, please don't try to be clever. Yeah, of course, there are a lot of people wrapping their head around asynchronous and multi-threading. So, there's that extension called P-threads. And when you look at the requirements of P-threads, you see that you need to enable the sense-threads-safety, VTS, which must be enabled on PHP build time. So, if you have a standard PHP installation that is not enabled, and thus you are not able to install or run P-threads. And P-threads are only for CLI-based applications. So, no web environment. There's a big warning that you shouldn't use it in web environments. So, it's not very suitable for our use case. So, again, it needs a custom PHP build. Not all extensions are thread-safe. So, when you enable this thread-safety, you may have problems with other extensions in PHP because not all extensions are thread-safe. Yeah, it's not working in web environment. You have to have a basic knowledge about multi-threading because P-threads is really multi-threading. And, yeah, probably you can leave out the basic because you need knowledge about multi-threading when you use P-threads. It's very complex. Yeah, you have a lot of feature and config overhead for such a simple task like an async request. Yeah, and you have to handle the complete process and thread management yourself. So, there's also another extension which is called process control or PCNTL that you can install. Again, you need to build PHP with a flag called enable pthl. Yeah, and it only works on Unix. So, no Windows, sorry. So, it needs a custom PHP build. It's not working on Windows. So, everybody who's developing on Windows machines cannot use it. You also need basic knowledge about Unix processes here. And the complete process management itself is up to you. You have to fork things, catch them again when they are finished. And there's another option which is called gearman. I think it's a little bit older already. Yeah, that extension has a lot of requirements because you need lip gearman, you need lip event, you need UID, and you need a gearman server. So, you need a completely other piece of infrastructure. Plus a PHP extension. So, again, a lot of overhead and a lot of configuration that we have and a lot of setup, especially on development environments to get that up and running. So, what do we actually want? We want to make asynchronous calls to PHP, obviously. Eventually, we want to get the responses of those calls. We don't want any additional infrastructure. We don't want any additional extensions. We would like to have web-like request data handling because we're used to it. So, when we are on a web service, we don't want to switch to CLI mode, right? We want to take advantage of OpCache because when you have a web request, OpCache comes into play. On the CLI mode, OpCache is already always disabled. Besides, you enable it, especially, but usually, when you use CLI mode, you don't have any OpCache. So, our background workers should not be exposed to the public. So, that's the point from the slide before. And we want to have some kind of tunable process management. Okay, now, what if I told you that we have a bullet-proof process manager already shipped with PHP? Yeah, and you're probably already using it. So, who's using PHP FBM? Who set up a PHP FBM by itself? Okay, good. So, yeah, we are talking about PHP FBM. It's the PHP 4CGI process manager. So, how it usually works when we are doing simple web requests we have a web server, Nginx, or Apache, or something like that, which is talking to PHP FBM via 4CGI protocol. And the PHP FBM has a default pool called WWW, sorry, which spawns one-to-end processes. So, that looks like this. So, most of the time, we have a master process and this is the default configuration. Yeah, and three worker processes that are waiting for requests. So, the initially idea of this talk and the library that I will introduce in the next slides came from a talk by Anna Blanketz in the PHP International PHP Conference in Berlin. He was showing a JavaScript application that was talking to PHP FBM by a library called JS 4CGI, whatever. And I said, okay, why can JavaScript talk to PHP FBM, but we can't because we are doing PHP, right? So, I had a look on GitHub if there is a PHP library that can talk to the 4CGI client or is 4CGI compatible and can talk to the PHP FBM client or server. And I actually found one from Pierre-Rick Charron and it was from 2005 or 2006, I think. I was not maintained for a couple of years and it was also missing, from my point of view, missing some important features. So, that was the point where I started to write or to rewrite the whole library so there's still original code from Pierre-Rick in that library and he's still in the author's list, but I think 90% of the code is new because his library was PHP 5, this is 7.1 upwards, so it works from 7.1 to 7.3. I recently released 2.5.0 last week with a little bit better error handling and, yeah, it's quite accepted already. So, what does this library do? Let's talk about those FBM pools. First thing that we had on the slide was we don't want to make our scripts that run in the background public, right? So, we also want to have it in an isolated process running and FBM gives us the possibility to create one or more pools which are isolating resources for us. So, what I can do is I can define a background pool which is configured to have no processes when it starts and can spawn up to n processes. Yeah, depends on how I configure it. So, I will show you a config later. So, what does PHP FBM do? PHP FBM sends the request to the pool and the pool is queuing the request, some kind of, and when the request is handled, it returns the response back to the PHP FBM and the PHP FBM returns the response back to the, in this case, the web server. So, this is how this background pool looks like. This is the minimal configuration. So, we say this is the name of the background. We need to set user and group for permissions, also for the listening permissions and we can choose if we use a local socket for the communication or if we use a network socket. So, we can also do a network socket in the port. So, we can also listen to port 9001 or something like that. So, the default pool is listening to port 9001 which is pretty cool because Xdebug is listening to the same port by default. Yeah, and what we are doing here is there are different kinds of process managers that you can adjust here. And when you say my process manager is the on-demand manager, then you won't have a master process and no child processes when this pool starts. So, the default value is dynamic and dynamic says, okay, we have a master process and we have child processes waiting for requests. So, and we say, okay, you can have maximum 100 children and you should close your children after 10 seconds of idle time. So, that is basically all we need. You just put that file in the configuration folder or confty of PHP FBM and restart PHP FBM and you're good to go. So, when you are using the client now, so this is the library now, we're just open a new Unix domain socket with the same pass that we had given in the pool configuration. Yeah, that's the only thing that you need. We have read and write and connect timeouts here that are default values so you don't have to give them. And then you just create the client. So, like I said, you can do the same thing with the network socket, which is just another class. Yeah, and just give it an IP and a port. So, yeah, that would be the config for the network socket. So, it's basically the same thing. And now, after we created the client, yeah, we just can send requests to a specific file on our PHP FBM server. So, very important is that file path here. So, it's like the script file that you have or that is your engine X calling on your PHP server. But you don't have an engine X and you don't have document root, how is it called? Yeah, document root is not replaced. So, that must be an absolute path with no path traversal in it and no relative path. So, it must be an absolute path on your server. Otherwise, PHP FBM won't found it because PHP FBM has no clue about any document roots or something like that. So, it just needs to find that file on your file system. So, yeah, there's a big hint in the read me because that is failing everyone. Yeah, and then you give the content, which is basically just a URL encoded string. And then you can send the request and get a response and get the body. So, in this case, of course, this is a synchronous request. And I want just to show you how the response looks like. So, this is the interface of the response. You get a request ID. You get the headers back. You can ask for specific headers. You get the body. You get the raw response, which is the body including the headers. And you get a duration. The duration is how long did your request took from request to response. So, let's send an async request. It's just another method. So, send it async. And this async request will give you a request ID back, which you can just print out. So, in this case, this is fire and forget, so we don't do anything with the response. If you want to get the response, you can say, okay, send the request asynchronously. Do something in the meanwhile and at the end of your script, just read the response for that particular request ID. And, yeah, print the body. So, I have a short demo later. So, at the end. What I, for example, added to the library is Colbex. Because that was the first question when I published the library. Hey, why are there no Colbex for responses and so on? Because everybody is using Colbex and JavaScript. And why don't you use Colbex and PHP? Yeah. So, you can, for each request, you can add a response Colbex and the failure Colbex. So, just like you know it from JavaScript, maybe. The response Colbex, of course, gives you the response back. And the failure Colbex will give you a throwable, which can be any error or exception. And, again, you just have to send async request and you will automatically notify about the, your Colbex will automatically notify. And, later on, I also added a path through Colbex because I had a use case where I sent something to the, to the worker script in the background. And I wanted to stream the output of that worker script directly to my browser. So, and that output comes in buffers, of course, because PHP has output buffering. And it's really hard to completely disable output buffering in PHP, trust me. Yeah. And that buffer is passed through Colbex. So, you can handle on your client side or on a requester side also the output of your worker. So, then there are multiple ways to wait for the responses. So, if you just wait, just call wait for responses, then all your Colbex will be notified. And it's basically the same as having a true, while true loop, asking if a particular request study has a response and then handle the response. So, I chose to have an inner and an outer loop so that you can hide everything about looping inside the client. But if you already have something like React PHP or something where you already have an event loop or something like that, you can integrate the client in that loop. That's why we have multiple methods to, yeah, ask for a response ready or not. So, that is a pretty easy example because it's only one request, right? So, but we have multiple requests in our use case. And what we can do is we can collect all request IDs from the async request, from the request. I can print them out and say, okay, read all the ready responses from those request IDs and print their body. The problem here is that will end up in sequential responding. So, the response will be looked like this. So, the first request will respond first, the second and the third, and so on. So, no matter how long each worker needs to do their job, the responses will come in the same order as the request. So, that's maybe not that what we wanted because we want to save time. And if one worker is faster than the other, then he should also respond faster. And that is what we call reactive. Yeah. So, what you can do is do the same thing, send three requests. And you can say, okay, I will wait for unhandled responses or until there are no unhandled responses anymore and then read the ready responses. It's the same thing. Again, it's in an auto-loop strategy. And that would end up in this, for example. So, if the second request is the fastest one, we will have different order of the responses. And overall, it is faster because let's assume this... Yeah, the first one is the slowest one. So, let's assume in the previous output when the first one is the slowest one, everybody else or all the other responses have to wait until the first one is finished and then we'll print their results. And in this case, we will get the second one as first. So, this is ordered by response time and this is exactly what we want. So, yeah. Like I said, again, this is in an auto-loop so we can say, wait for all the responses or ask for unhandled responses and handle the ready responses. So, you see we have different namings here for wait and handle and read and so on. So, always when you see a read method, it tries to read the response directly and the handle or returns the response and the handle methods are calling the callbacks. So, if you add any callbacks, you will need to have to use the handle methods. So, okay. I promise a quick demo. Yeah. So, I'm a backend developer so I suck at frontend and I just want to show you. So, basically it's the frontend of our web service. I can create a single PDF. On the right side is the output of the PHPFBM pool in real time, hopefully. So, there's a background pool client that is spawned and it's going away after a couple of seconds because I told him so. Now, we have the example with multiple PDF creations but in the same order as the requests. So, as you can see, we have 21 processes now and it takes some time because there's a random sleep in the worker so all the requests have a different length. Actually, they're all doing the same thing. So, we need roughly 19 seconds and yeah, let's wait until the workers are going away. And now, we have the multi-request in reactive order. So, as you can see, the numbers are not in order and we need, hopefully, very much less time. Yeah, 12 seconds, okay. Before, where I ate seconds, so it was very low for us. Okay, and we actually created... No, not that PDF. Woodward. So, we just created a lot of PDFs in the background. Actually, while there was no PDFs because WK HDL2 PDF library sucks. It cannot handle so many requests at the same time. So, as you have seen, there is very little configuration that you have to do and probably it's configuration that you already know because you have set up a PHP-FPM pool. That library, you just have to pull via Composer and then you can play around with the request of the responses and can do things in the meanwhile. Any questions? Yes.