python-trio / trio

Trio – a friendly Python library for async concurrency and I/O
https://trio.readthedocs.io
Other
6.06k stars 330 forks source link

make trio.BlockingTrioPortal and trio.Lock work together #680

Open njsmith opened 5 years ago

njsmith commented 5 years ago

In this SO question, @Nikratio asks about how to share a lock between Trio and a thread.

There are some advantages to using trio.Lock, and having the thread access it through a BlockingTrioPortal – in particular, it means that the Trio-side code can use cancellation as normal. (It's also maybe cheaper? I haven't measured, but it seems plausible.)

However, if you try this, you'll get bitten in a surprising way: trio.Lock tracks which task acquired it, and wants that task to release it. This is intended to help avoid mistakes and give us the option of implementing deadlock detection later (#182), but here it creates a problem: if you do portal.run(lock.acquire), and then later portal.run_sync(lock.release), then you'll get an error, because the BlockingTrioPortal uses a different backing task for each call, so from the Lock's perspective the task that's trying to release it is some random task unrelated to the one that acquired it.

This is unfortunate, we should fix it somehow. Some options:

njsmith commented 5 years ago

(It'd be great if folks who have real uses for BlockingTrioPortal, like @nikratio and @zeed, could chime in with more details about what their code looks like.)

agronholm commented 5 years ago

In my apps, I've needed the ability to run async tasks from threaded code when a function primarily run inside a worker thread needs to use an existing async service. Our Exchange integration is a good example of this. Exchangelib's API is horrendous and is highly unsafe to run in an async task since at any point it could start an outbound connection.

agronholm commented 5 years ago

Doing the same from an SQLAlchemy flush/commit listener is an even better example.

Nikratio commented 5 years ago

My code currently uses thread/task locks only within condition objects, and I think I am fine at the moment. However, I depend on being able to acquire and release from different threads for other locks that are currently thread-only. My plan was to port those to trio, but I guess I better wait with that :-).

How about adding something like trio.DumbLock, which is a separate, non-recursive lock that can be used when acquiring/releasing by different threads is specifically desired?

njsmith commented 5 years ago

How about adding something like trio.DumbLock, which is a separate, non-recursive lock that can be used when acquiring/releasing by different threads is specifically desired?

Since Guido's not using his time machine currently, I borrowed it to implement this as trio.Semaphore(1, max_value=1).