3

So basically, I am using cURL to call APIs that I can't implement natively in PHP. When I make a call though, it deadlocks my PHP session. I cannot connect to my website from another tab in my browser. If I delete the session cookie, I can connect fine. Before, when I didn't have a timeout in cURL, this would last indefinitely.

Is this just how the PHP implementation of cURL works, or is there a way around it?

NobleUplift
  • 5,631
  • 8
  • 45
  • 87
  • Here's a good blog post on sessions and cURL. [link](http://www.smooka.com/blog/2009/07/24/maintaining-php-session-when-using-curl/) – Ryan Smith Apr 25 '14 at 15:06
  • Yes, that's how it works. PHP does not support threads, so there's no (easy) way to avoid this kind of blocking. – Kryten Apr 25 '14 at 15:06
  • Since there is only one session it can't deadlock. It might keep the lock and not release it until it times out or is killed, but that's not quite a deadlock. – Halcyon Apr 25 '14 at 15:06

2 Answers2

4

As mentioned here, you might want to try to do the cURL request without having the session open, e.g. by doing session_write_close() before sending the request and session_start() after you've handled it.

Community
  • 1
  • 1
thejh
  • 44,854
  • 16
  • 96
  • 107
  • 1
    This is an accident waiting to happen - what if the `session_start()` after the cURL is blocked by another request? – Eugen Rieck Apr 25 '14 at 15:15
  • @EugenRieck As long as none of your scripts block a session for excessive amounts of time, I can't see any problem with that. If you say that the `session_start()` after making the cURL request is a problem, you have to consider every other `session_start()` call equally problematic. – thejh Apr 25 '14 at 15:19
  • If I open the session, read all the values from it, and then immediately close it, then the other request should only be blocked by a few milliseconds, right? – NobleUplift Apr 25 '14 at 16:22
1

This has nothing to do with cURL: Basically every long-running operation is prone to the same problem.

Here is how we typically deal with this:

  • The request, that should start the process (e.g. start the cURL command) should NOT do so, but just authenticate it and create a one-time ticket for it, storing it in the session and giving it back
  • On return, the client should now request running the cURL, using the one-time ticket, but NOT the session. This keeps the session unlocked. Results must be stored intermediately.
  • After finishing the long-running process, the client uses the session to collect the intermediately stored results.
Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
  • So the client has to send three requests instead of one? Sure, it works, but it doesn't look very clean to me... – thejh Apr 25 '14 at 15:12
  • 1
    The main point is, that the long-running process is completely isolated from the normal control flow. While this introduces (IMHO manageable) complexity, it makes for robustness and modularity, by separating the incompatible concepts of "session" and "long running request" – Eugen Rieck Apr 25 '14 at 15:14
  • @thejh It's quite clean, because a client isn't hung up waiting for a long-running operation. More complex--yes. But more complex does not mean less clean. Trying to guess how long an operation might take so you can set the "right" timeout value is far messier, I think. – Brian Warshaw Apr 25 '14 at 15:15
  • @BrianWarshaw The reason why I think that it's not so clean is that you're working around a server-side issue (your software can't handle parallel requests belonging to one session) in a way that involves the client. In my opinion, a server-side problem like this should also be solved on the server. – thejh Apr 25 '14 at 15:17
  • @thejh While I see the point, I do not consider it 100% valid: Using a synchrous client against a synchronous and server, and an asynchronous client against an asynchronous server seems quite natural to me. – Eugen Rieck Apr 25 '14 at 15:19
  • Would you feel better if the "ticket" was stored in a database and the client polled for status until completion? That's not a rhetorical question, either--just trying to see if I understand your specific concern. – Brian Warshaw Apr 25 '14 at 15:19
  • @BrianWarshaw Storing the ticket is what we normally do! This gives the possibility to show progress etc. – Eugen Rieck Apr 25 '14 at 15:21
  • That's generally my team's approach, too--why I asked :-) – Brian Warshaw Apr 25 '14 at 15:22
  • @BrianWarshaw Depends on the scenario. If the client could just as well wait for the response, and the response is likely to arrive within a few seconds, I would still not like it. But if the page might be reloaded by the user before there's a response or it's not practical for the client to maintain a connection, I would like the polling solution. – thejh Apr 25 '14 at 15:23
  • So basically, cURL is being run by my API, and the API is requested by the website. You're saying that I should move the cURL call outside the API so that it's called directly on the website? And why would the client need to use the session to collect the results? – NobleUplift Apr 25 '14 at 19:11
  • No, this is not what I mean: I mean to split the API one long-running call into two short-running synchronous calls and a third long-running asynchronous call: In pseudo-API: 1. "GetTicketForCURL(URL, whatever)->ticket" (using session), 2. "ExecuteTicket(ticket)->status" (not using session) and 3. "CollectTicketResults(ticket)" (using sessioin). If the results from the cURL call are **not** needed inside the session on the server side, you can fold 2 and 3 into a single call. – Eugen Rieck Apr 25 '14 at 19:16
  • Yes, the results of the cURL call have nothing to do with the session, I just happened to have it open at the same time. Regardless, `session_write_close()` prevented the deadlock for me. This isn't smart code nor is it indicative of our entire API. It's just a dumb hack. – NobleUplift Apr 28 '14 at 15:05