Skip to content
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

vips_cache_remove() is called by signal without mutex lock #1484

Closed
MaxKellermann opened this issue Nov 28, 2019 · 3 comments
Closed

vips_cache_remove() is called by signal without mutex lock #1484

MaxKellermann opened this issue Nov 28, 2019 · 3 comments

Comments

@MaxKellermann
Copy link

Every access to vips_cache_table must be protected by having the mutex vips_cache_lock locked. However, here a signal gets connected:

entry->invalidate_id = g_signal_connect( operation, "invalidate",
G_CALLBACK( vips_cache_remove ), NULL );

This signal will be emitted without locking vips_cache_lock here:
g_signal_emit( operation, vips_operation_signals[SIG_INVALIDATE], 0 );

Thus vips_cache_remove() gets called without the lock, however it also doesn't acquire the lock, but accesses vips_cache_table:
static void
vips_cache_remove( VipsOperation *operation )
{
VipsOperationCacheEntry *entry = (VipsOperationCacheEntry *)
g_hash_table_lookup( vips_cache_table, operation );

This will eventually crash.
Note that adding the lock to vips_cache_remove() will still crash, because that will just wait until another thread releases the lock, and meanwhile the other thread may have destroyed the object.
(From GLib API documentation, it is not clear whether the whole g_signal library is thread-safe at all. And even if so, what happens if you disconnect a signal that is currently already running? Will it wait for completion? If yes, then you have just produced a deadlock, because g_signal_handler_disconnect() is called while holding the lock.)

jcupitt added a commit that referenced this issue Nov 28, 2019
This patch makes operation cache invalidate advisory rather than
immediate. Operations set a mark on cache entries meaning "this entry is
no longer valid", then the entry is removed next time the operation
is looked up.

This breaks the loop (now the cache can remove operations, but operations
can't remove cache entries), so it should be safer (I think). Everything
is inside a mutex, at least.

see #1484
@jcupitt
Copy link
Member

jcupitt commented Nov 28, 2019

Hi Max,

Yes, you're right, this should be improved.

How about making invalidate advisory rather than immediate? Operations could set a mark on cache entries meaning "this entry is no longer valid", then the entry would be removed next time that operation is looked up.

This breaks the loop (now the cache can remove operations, but operations can't remove cache entries), so it should be safer (I think). Everything is inside a mutex, at least.

I put your name on the change, I hope that's OK.

@jcupitt
Copy link
Member

jcupitt commented Nov 28, 2019

More generally, you're also right about signals and threading, it's a murky area.

Emit holds a ref to the object that's emitting during the process, and ref/unref is threadsafe, but that often won't be enough. Doing more than the minimum in a signal handler is asking for trouble.

@jcupitt
Copy link
Member

jcupitt commented Dec 7, 2019

I think this is at least partly addressed now in gut master. Please open a new issue if there is still a problem.

@jcupitt jcupitt closed this as completed Dec 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants