Remove a RequestContext deadlock
authorMarc Celani <marccelani@fb.com>
Sat, 4 Mar 2017 06:27:50 +0000 (22:27 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Sat, 4 Mar 2017 06:34:33 +0000 (22:34 -0800)
commit00eb2ce00f68bdb274844f1c6bb29ca6b8bbf6cf
tree284a1bfb59d950aff44f64d0c03409f180ca5a43
parent97377ac82a909b7d81537f5968381ea6af367a94
Remove a RequestContext deadlock

Summary:
It is not unusual to write a RequestData that will send a request to a backend service upon destruction (for example, to log the request data). This typically involves calling EventBase::runInEventBaseThread() to do the logging. Normally, if you wait until the RequestContext is destructing for this to happen, everything works fine. However, if you explicitly call clearContextData(), then this can cause a deadlock. clearContextData() holds the write lock on the data_. Imagine the NotificationQueue thread tries to set the context (for some other reason), and it is waiting on the read lock on the data_. It does so while holding a lock on its own queue. Now, the clearContextData() calls the destructor while holding the write lock on data_, and the destructor calls runInEventBaseThread, which grabs the queue's lock. Deadlock.

This can either be fixed in NotificationQueue or RequestContext. I decided to fix it in RequestContext because there may be other deadlocks that don't involve NotificationQueue (see the test plan).

There are other deadlocks in RequestContext if you call write methods in RequestData::onSet or onUnset. But fixing this would require a massive perf regression (I imagine using shared_ptr instead of unique_ptr, and copying the ptrs under readlock and calling the callbacks after releasing the lock - yuck!) So instead, warn people against doing that.

Reviewed By: yfeldblum

Differential Revision: D4655400

fbshipit-source-id: cfebe696953a19dc4dba49976bbcd0ec1363bc42
folly/io/async/Request.cpp
folly/io/async/Request.h
folly/io/async/test/RequestContextTest.cpp