-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FEATURE: Allow jobManager to be interrupted by system SIGINT #48
base: main
Are you sure you want to change the base?
Conversation
As I had no Idea how this kind of behavior can be tested I wrote some very stupid application to ensure my assumptions. If you would like to see this, or have an idea how this could be tested (the JobCommand is currently not tested at all), I am thankful for any suggestion |
Any feedback is highly welcome. Otherwise I would need to maintain an own fork, which I would love to avoid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @fcool,
thanks a lot for this contribution and sorry for the late reaction.
I think this totally makes sense but I need to get more into PCNTL to fully understand it. Left some questions and comments for now
@@ -84,6 +90,12 @@ public function workCommand($queue, $exitAfter = null, $limit = null, $verbose = | |||
} | |||
try { | |||
$message = $this->jobManager->waitAndExecute($queue, $timeout); | |||
$this->jobManager->interruptMe(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this method called here? This will be invoked for every successfully processed message in the queue. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. By calling this message, it allows the jobManager to be interrputed in this (and only in this) moment. And this is definitely a safe moment, because the last message has been "completed" (maybe by timeout, or successful execution). Usually it will probably simply do nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I think my confusion came from the name since this doesn't always actually interrupt obviously.
* | ||
* @throws InterruptException | ||
*/ | ||
public function interruptMe(): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: I find the name interruptMe
a little weird. Why not just JobManager::interrupt()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming things is always the hardes task in programming. The idea had been, to find a name, which makes clear for the using classes, that this is more a less a signal to the job manager, that this would be a safe point for an interruption. Your suggestion is totally fine for me, if this makes more sense for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused by the imperative verb. But since this doesn't actually interrupt anything I would suggest to call this something like processPcntlSignals()
or so instead.. But this leaves me wondering: Why do we need this methond in the JobManager
at all? Can't we just move this line to the CommandController?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is there to make it easy for queues to signal their "safe state" to the job manager. They should not need to be aware of the CommandController. And if no Signal handler was registered, it just do nothing, so means no harm to call.
If you think the signal handler could be managed by the JobManager I'm with you. I had reasons to nut put it there - but I currently do not remember
@@ -74,6 +75,11 @@ public function workCommand($queue, $exitAfter = null, $limit = null, $verbose = | |||
} | |||
$this->outputLine('...'); | |||
} | |||
if (function_exists('pcntl_signal')) { | |||
pcntl_signal(SIGINT, static function () { | |||
throw new InterruptException('Interrupted by SIGINT'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw new InterruptException('Interrupted by SIGINT'); | |
throw new InterruptException('Interrupted by SIGINT', 1602072222); |
I forgot in my first round: This is missing a unique exception number. We usually add the unix timestamp to the throw
calls in order to be able to trace them back to the calling side more easily
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for clarifying.
A ticket with a minimal example would be awesome in order to grasp & communicate this feature better. Would you be up for creating one?
What do you mean with "minimal example"? My struggle here is: On the command line it is quite easily testable. Just press CTRL+C after starting a worker command. In the current state it will interrupt anything immediately. But thats not that nice, if the job was already running for several hours and is near its completion. So my example project has a simple sleep job. As said, if have no idea how to test this with a single threaded php-unit test. As this would run unnecessary long, call a command worker in unknown environment loosing the test scope, etc... But maybe at this place its just me being unexperienced. |
But of course, I can open an issue, when this is the missing part. ;) |
Right. But for that scenario we wouldn't need to adjust the
That's exactly the kind of example I was thinking of :) |
No problem. I build specifically for this part an own test project. Do you want it with, or without? I would attach my Test scenarios to the readme.md so you could repeat, what I played with |
I just would like to understand this feature better. So a little code snippet would be enough:
|
Understood. And completely feel the urge. That asynchronous stuff is hard to wrap ones head around. Especially with the single threaded PHP processes. I had to learn about pcntl and the way it handles interruptions, too. The usual advice is to set I will publish the test project and with it some lines documenting, in which way the proposed changes changes the game. Cannot promise to do it today. But at latest tomorrow. |
Wow... sorry. Got unexpectedly a bit busy. Will deliver the next days, if not today (bringing some pressure on me in this topic ;) ) |
Here is the example Project: |
@fcool thanks a lot for putting so much effort into an example project. But do I get it right that it actually doesn't do anything special but providing a patch for the doctrine implementation?
in order to interrupt processing if requested by the user. |
Now you lost me completely. By patching doctrine you allow to the doctrine queue to be interrupted during its "waiting" phases. As in default this is 60 seconds it would be only be interruptable once all 60 seconds, as long as there are no short tasks. Of course the PCNTL extensions have to be installed. |
Jast saw this, and even thought it is old by now… it still seems a great addition. @fcool, would you bring this up to date as needed? |
This is a first draft, to checkout if this is something you are even going to accept. (It is fully functionally though - but it needs to be documented - which I'd love to do, if you accept)
This change allows the JobWorker to be interrupted on the Command-Line by pressing CTRL+C or sending a SIGINT-Signal at a safe state (it will only be interrupted if the last job had been completed successfully or a timeout occured)
Additionally the JobManager provides a new Message queues might use to signal, they are now safe to interrupt (ideally before they go sleeping in their own "waiting for timeout" handling)