Ben Darnell <ben.darn...@gmail.com> added the comment:

> To be clear, by "cancel" you are not talking about Future.cancel().  Rather, 
> your handler causes all running tasks to finish (by sending a special message 
> on the socket corresponding to each running task).  Is that right?

Correct. My tasks here are calls to functions from the `select` module (one 
select call per executor task), and cancelling them means writing a byte to a 
pipe set up for this purpose. 

The select calls could be given a timeout so there is never an infinite task, 
but that's not ideal - a timeout that's too low has a performance cost as calls 
timeout and restart even when the system is "at rest", and a too-long timeout 
is still going to be perceived as a hanging application. 

> * it does not make sure the task associated with the socket finishes (no way 
> of knowing?)
> * so if a task hangs while trying to stop then the running thread in the 
> ThreadPoolExecutor would block shutdown forever
> * similarly, if a task is stuck handling a request then it will never receive 
> the special message on the socket, either blocking the send() in your handler 
> or causing ThreadPoolExecutor shutdown/atexit to wait forever

Correct. If the task were buggy it could still cause a deadlock. In my case the 
task is simple enough (a single selector call) that this is not a risk. 

> * it vaguely implies a 1-to-1 relationship between sockets and *running* tasks
> * likewise that pending (queued) tasks do not have an associated socket 
> (until started)

Each task is associated with a selector object (managing a set of sockets), not 
a single socket. There is only ever one task at a time; a task is enqueued only 
after the previous one finishes. (This thread pool is not used for any other 
purpose)

> * so once your handler finishes, any tasks pending in the ThreadPoolExecutor 
> queue will eventually get started but never get stopped by your handler; thus 
> you're back to the deadlock situation

In my case this one-at-a-time rule means that the queue is always empty. But 
yes, in a more general solution you'd need some sort of interlock between 
cancelling existing tasks and starting new ones. 

> Alternately, perhaps ThreadPoolExecutor isn't the right fit here, as implied 
> by the route you ended up going. 

Yes, this is my conclusion as well. I filed this issue because I was frustrated 
that Python 3.9 broke previously-working code, but I'm willing to chalk this up 
to Hyrum's law and I'm not sure that this is something that ThreadPoolExecutor 
should be modified to support.

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue41962>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to