0

My understanding of all the parts here is weak. I want an interactive PHP web app X, to allow users to start an often lengthy server-side PHP script called Y. Because Y can take a long time to complete and produces no directly relevant output, I don't want X to block on Y completing. So my desired goal is for X to use a socket to "fork" Y off on its own connection to the server. Up until recently, code in X that I can simplify to the following was sufficient to launch and complete script Y on my server:

$fp = fsockopen ("ssl://www.myserver.com", 443);

if ($fp) {
    
    echo "Y is starting";
    
    fwrite($fp, "GET /path/SCRIPT-Y.php HTTP/1.1\r\n"
        ."HOST: www.myserver.com\r\n"
        ."\r\n");
        
        
    fclose($fp);
    }

This is the approach recommended e.g. here.

However, some recent change to my (externally hosted and largely opaque) server environment (I don't know what) has caused this logic to start to fail. Most if not every time, now, script Y fails to execute, despite "Y is starting" indicating a successful fsockopen. (Above code is a minimalist reduction of my actual code; in actual code, fsockopen() watches errNo and errStr but they are always zero/null.)

Empirically, I see two behaviors from slight changes to this code:

  1. with the socket open, if I block and report output from the connection until feof(), script Y actually DOES complete. The loop lasts until server timeout or, if I add a "Connection: Close" header, until script completion, but both are unacceptably long for code running in X.

  2. if I remove the fclose() call, this code returns immediately, and my server successfully runs Y.

Putting these two observations together, I hypothesize that closing the connection in X somehow kills not only the connection but the process Y associated with it on my server. I can understand that as theoretically useful in a stateless / zero-side-effect paradigm -- if there is no consumer of output, there is no need to execute the program -- but here obviously I'm interested in Y's side-effects rather than its direct output so need its process to stay alive.

I feel like adding a Close:Connection header so the server closes the connection "when Y is done" but not calling fclose() in X gives me the behavior I want. However, orphaning the open socket connection -- not calling fclose() -- feels very wrong; X runs "forever" and so would leak a socket-descriptor every time users launch Y. (If I was reasonably guaranteed the number of sockets I could orphan was in the hundreds of thousands, I could go this route though...Y is actually a Google Calendar event-posting transaction; and I know in life of app my users will post thousands but not hundreds of thousands of calendar events between server restarts :-).)

What is the transaction pattern I'm really looking for, here, and how do I implement it robustly in PHP? Do I really have to track a queue of open connections in X and poll them for Y's self-reported completion before closing each manually? I am hoping to avoid polling or launching threads; ideally I just want to "launch and forget." (It would be fine for a Y process so launched to "time out" after a while; I just don't want my user in X to have to block on that time out!)

Thanks in advance for any help.

  • 1
    A more robust approach for this is to build worker scripts that continuously run and wait for jobs. I would strongly suggest avoiding `fork()`. Worker scripts and job queues should be something with many different solutions for. – Evert Oct 04 '21 at 17:02
  • If you don't want to bother setting up a queue, you can have script Y make jobs by creating files on the backend. Than have script Z starting via cron every minute or so, picking up several jobs and processing them. – Nenad Mitic Oct 04 '21 at 20:02
  • @NenadMitic Unfortunately, my host (NameCheap) doesn't permit cron more often than once every 5 minutes, so it lacks a UI responsiveness I'm hoping for (user X clicks "start" and side-effect of script Y starting is immediately apparent, even if script completion takes some time). – Nicholas Jackiw Oct 05 '21 at 18:09
  • @Evert but, naively, isn't my web server exactly a worker script that runs continuously awaiting jobs, and my HTTP request a "job" that happens in this case to take the form of "execute PHP script Y?" I understand I could architect this myself, polling via cron, but what is inherently wrong or unrobust about using an HTTP request to do what I want? (Imagine for example Script Y is an arbitrary web service that might be on a completely different host than X; in that case, the approach I'm trying to debug seems essential, not optional...) – Nicholas Jackiw Oct 05 '21 at 18:12
  • @NicholasJackiw you are not wrong. You could do an internal HTTP request, but a few things that are clearly different are that HTTP is a request/response model, worker scripts typically asynchronously report results, it's also typical that these time out, so you need to remove all timeouts for long-running scripts which may be undesirable and you still shouldn't fork(). – Evert Oct 05 '21 at 18:28
  • Lastly, jobs remain in queues until a worker is available to handle it. This might give you better scaling properties. Nothing goes wrong if all workers are temporarily busy/down. – Evert Oct 05 '21 at 18:29

0 Answers0