Skip to content
This repository was archived by the owner on Dec 9, 2022. It is now read-only.
This repository was archived by the owner on Dec 9, 2022. It is now read-only.

Garbage collection can cause deadlocks #23

@Burgestrand

Description

@Burgestrand

@ike18t reported an issue via mail about spotify causing deadlock, and found a fix:

I think I finally figured out why spotify kept freezing for my app. I noticed all of the (track|artist|user)_release getting logged from your gem. Little did I know that there is a free method on the objects to release the memory and using it once I'm done with the object seems to prevent all of the auto releases which seemed to occasionally cause a freeze if the player tried to start before the releases finished.

Now, there must be a bug in the Spotify gem here, so here’s what I speculate:

It has never occurred to me that this kind of race condition might happen, but now that I think on it I’m fairly certain I know what happens. Ruby (MRI) runs garbage collection in a separate thread, but it’s probably so that no other Ruby code runs while garbage collection is happening, so all code is halted.

In another area, I have a lock around the Spotify functions. It will be locked prior to calling any Spotify function, so no more than one thread at a time may call a Spotify function because libspotify is not thread-safe.

Now, assuming you call a Spotify function (any function, but the longer it takes the higher chances of the freeze happening), and garbage collection happens to trigger right after your program acquires the Spotify lock, the lock will still be locked while garbage collection is running. Now, when garbage collection tries to free a Spotify pointer, it will attempt to acquire the Spotify lock, but it cannot because another thread is already holding the lock.

After this we have our main thread that holds the lock which will not release the lock until it runs again. Unfortunately, Ruby will not let our main thread run until garbage collection is finished. Garbage collection cannot finish because it is waiting for the lock hold by the main thread.

I believe I can fix this by creating a reaper thread. It will spin in the background forever, just waiting for a queue of pointers to be freed. In the finalizers for each pointer I will add the pointer address to the reaper queue, and they will be scheduled for release after garbage collection has successfully run. This should allow garbage collection to finish, which would yield control back to the main thread, which in turn will release the lock which allows the reaper thread to perform garbage collection!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions