Closed
Bug 1139225
Opened 10 years ago
Closed 10 years ago
Add locking for imgRequest member variables that can be accessed from multiple threads
Categories
(Core :: Graphics: ImageLib, defect)
Core
Graphics: ImageLib
Tracking
()
RESOLVED
FIXED
mozilla39
Tracking | Status | |
---|---|---|
firefox39 | --- | fixed |
People
(Reporter: seth, Assigned: seth)
References
Details
Attachments
(4 files, 3 obsolete files)
3.52 KB,
patch
|
tnikkel
:
review+
|
Details | Diff | Splinter Review |
1.32 KB,
patch
|
Details | Diff | Splinter Review | |
31.23 KB,
patch
|
Details | Diff | Splinter Review | |
1.24 KB,
patch
|
Details | Diff | Splinter Review |
As explained in bug 1137002, we currently have racy accesses to imgRequest member variables from multiple threads. These racy accesses all originate from the fact that imgRequest::OnDataAvailable (almost) always runs off-main-thread now, but touches member variables that also get accessed from the main thread.
I've tried to separate out what I could, but the remaining task is nontrivial: we need to ensure that all of those accesses are synchronized, with no significant performance impact, and do so in a way that's maintainable.
Given how ugly the original code is, the maintainability part in particular is a bit tricky. My highest priority is to simplify reasoning about this code in the future from a concurrency standpoint, so an important part of this patch will be to try to isolate the code in OnDataAvailable that needs to deal with synchronization in one place, and disentangle it from the other logic. I've already spent more time than is really justified fussing around with the exact structure of the code, so I don't want to aim for perfection here, but hopefully the final state of the code will be something that is at least easier to reason about when refactoring further.
Assignee | ||
Comment 1•10 years ago
|
||
Part 1 just removes an unused flag so we don't have to worry about it in the
changes that follow. This is so trivial it didn't seem to justify its own bug.
Attachment #8572299 -
Flags: review?(tnikkel)
Assignee | ||
Comment 2•10 years ago
|
||
In this part we stop automatically forwarding ProgressTracker::OnImageAvailable
to the main thread.
The new code in imgRequest will be immediately replaced in part 3; the
motivation here is to make it possible to merge the
ProgressTracker::OnImageAvailable runnable and the imgRequest::SetProperties
runnable (SetPropertiesEvent) , since in part 3 we need to rework that runnable
anyway and it's more efficient to merge them.
Attachment #8572302 -
Flags: review?(tnikkel)
Assignee | ||
Comment 3•10 years ago
|
||
OK, here's part 3. I'd really have liked there to be more parts, but it was
tricky in practice to split a lot of this stuff out since many of the changes
depend on each other.
The most important changes are:
- We add a mutex to each imgRequest. Several of the member variables are now
protected by this mutex. (The list is documented in imRequest.h.)
- Since virtually all of the flags needed to be protected, and things that are
protected cannot share the same bitfield with things that aren't, I decided to
just make all flags protected. The only flag for which this required adding
new, in some sense unnecessary locking was mIsInCache, but this is a
time/space tradeoff; storing mIsInCache separately to avoid the lock would
have increased the size of every imgRequest object, and I don't anticipate the
methods that touch it being called very often at all, or there being any lock
contention resulting from this change.
- I added a private method, imgRequest::GetImage(), because there were multiple
places where we only took the lock to grab the current value of mImage.
- As mentioned in comment 0, it was important to me to separate the
synchronization logic in OnDataAvailable from the other logic, so I took the
approach of making the code that's directly in OnDataAvailable mostly about
synchronization. OnDataAvailable now delegates the real work to other code,
most importantly the new function imgRequest::PrepareForNewPart.
- PrepareForNewPart does all of the work that OnDataAvailable did when
mNewPartPending was set. Because it's not a method of imgRequest, we can be
sure that it doesn't engage in any inappropriate mutation of member variables.
That should make it harder to mess this up again in the future. Instead,
OnDataAvailable passes in the state that PrepareForNewPart needs, and it
returns all of the new state that we need to update imgRequest with using the
new type NewPartResult.
- As I mentioned in the comment for part 2, the runnables used for
OnImageAvailable and SetProperties are merged into a single runnable,
FinishPreparingForNewPartRunnable. This runnable takes ownership of the
NewPartResult that PrepareForNewPart returned, and takes care of any final,
main-thread-only work. Some other code had to be updated to make this work
smoothly - in particular, SetProperties needed a different signature.
After this patch, we will be in a position where all the state that
OnDataAvailable touches is synchronized. Indeed, this patch is pretty
conservative about that, because I don't see any reason to behave otherwise
unless there's a performance benefit - we could, for example, rely on the
implicit synchronization happening elsewhere between OnStartRequest and
OnDataAvailable and touch mIsMultiPartChannel outside of the lock, but there
would be no performance benefit, and it would make the code harder to reason
about.
I do feel like things are still uglier than I'd like them to be, but we'll
continue tweaking and refactoring this code over time.
Attachment #8572313 -
Flags: review?(tnikkel)
Updated•10 years ago
|
Attachment #8572299 -
Flags: review?(tnikkel) → review+
Assignee | ||
Comment 4•10 years ago
|
||
One thing I forgot to mention: FinishPreparingForNewPart is also responsible for updating imgRequest::mContentType. That's another reason I wanted to merge all these different runnables; we would've had three total if each task got a separate runnable.
Updated•10 years ago
|
Attachment #8572302 -
Flags: review?(tnikkel) → review+
Assignee | ||
Comment 5•10 years ago
|
||
Updated part 1 to also remove method declarations for onload-blocking-related
methods that aren't defined anywhere.
Assignee | ||
Updated•10 years ago
|
Attachment #8572299 -
Attachment is obsolete: true
Comment 6•10 years ago
|
||
Comment on attachment 8572313 [details] [diff] [review]
(Part 3) - Make OnDataAvailable threadsafe
Review of attachment 8572313 [details] [diff] [review]:
-----------------------------------------------------------------
::: image/src/imgRequest.cpp
@@ +426,5 @@
> + bool isInCache = false;
> +
> + {
> + MutexAutoLock lock(mMutex);
> + isInCache = mIsInCache;
Is it safe with this small scope of mutex while we call this and imgRequest::SetIsInCache() at the same time?
Assignee | ||
Comment 7•10 years ago
|
||
Pushed a try job:
https://treeherder.mozilla.org/#/jobs?repo=try&revision=35d58ca2e4e6
Assignee | ||
Comment 8•10 years ago
|
||
(In reply to Jerry Shih[:jerry] (UTC+8) from comment #6)
> Is it safe with this small scope of mutex while we call this and
> imgRequest::SetIsInCache() at the same time?
Yes, the small scope is intentional. See the second bullet point in comment 3. mIsInCache is not accessed anywhere but on the main thread. However, it's a member of a bitfield which is guarded by the lock. In order for that to be safe, all bitfield members must only be accessed with the lock held.
Assignee | ||
Comment 9•10 years ago
|
||
This new version of part 3 fixes two issues:
- The switch from StartDecoding to RequestDecode meant that we were calling a
method that wasn't supposed to be running off-main-thread, and dying with an
assertion. This was fixed for StartDecoding by dispatching a runnable to the
main thread if necessary, but RequestDecode doesn't do that. I've addressed
this by running RequestDecode in FinishPreparingForNewPart, which consolidates
yet another runnable into the FinishPreparingForNewPartRunnable. (In a
followup I'm going to remove the code in StartDecoding that dispatches a
runnable to the main thread if necessary, since I'm pretty sure it only exists
to support this one call.)
- There was a typo in the code that called RequestDecode in OnDataAvailable; we
were calling it via |result.mImage| after calling |result.mImage.forget()|,
when we really should've been calling it via |image|. This got fixed by the
fix for the other issue. This problem was the thing that was making everything
red and orange on try.
Attachment #8575629 -
Flags: review?(tnikkel)
Assignee | ||
Updated•10 years ago
|
Attachment #8572313 -
Attachment is obsolete: true
Attachment #8572313 -
Flags: review?(tnikkel)
Assignee | ||
Comment 10•10 years ago
|
||
(Bug 1141819 is the followup about removing the implicit runnable dispatch in StartDecoding.)
Assignee | ||
Comment 11•10 years ago
|
||
Here's a new try job:
https://treeherder.mozilla.org/#/jobs?repo=try&revision=86a7d9015abb
Assignee | ||
Comment 12•10 years ago
|
||
The new try job is looking pretty green.
Comment 13•10 years ago
|
||
Comment on attachment 8575629 [details] [diff] [review]
(Part 3) - Make OnDataAvailable threadsafe
> NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
> if (!mRequest) {
>- NS_ASSERTION(mpchan,
>- "We should have an mRequest here unless we're multipart");
>- nsCOMPtr<nsIChannel> chan;
>- mpchan->GetBaseChannel(getter_AddRefs(chan));
>- mRequest = chan;
>+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
>+ MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart");
>+ nsCOMPtr<nsIChannel> baseChannel;
>+ multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
>+ mRequest = baseChannel;
> }
We already have multiPartChannel defined above in the same way in this function. Why can't we use the existing one?
Attachment #8575629 -
Flags: review?(tnikkel) → review+
Assignee | ||
Comment 14•10 years ago
|
||
Rebased.
Assignee | ||
Updated•10 years ago
|
Attachment #8575629 -
Attachment is obsolete: true
Assignee | ||
Comment 15•10 years ago
|
||
Pushed to inbound:
3f5eae4fac0f Seth Fowler — Bug 1139225 (Part 3) - Make OnDataAvailable threadsafe. r=tn
fcaed7860ea4 Seth Fowler — Bug 1139225 (Part 2) - Dispatch OnImageAvailable to the main thread manually in imgRequest. r=tn
fbd89012eda7 Seth Fowler — Bug 1139225 (Part 1) - Remove unused imgRequest::mBlockingOnload flag. r=tn
Assignee | ||
Comment 16•10 years ago
|
||
(In reply to Timothy Nikkel (:tn) from comment #13)
> We already have multiPartChannel defined above in the same way in this
> function. Why can't we use the existing one?
Thanks for the review!
I didn't want to fix this and have to run another huge try job, but I will push a followup that fixes it.
Assignee | ||
Comment 17•10 years ago
|
||
Pushed a followup to replace MOZ_OVERRIDE with override and MOZ_FINAL with final:
https://hg.mozilla.org/integration/mozilla-inbound/rev/f687743cc499
Comment 18•10 years ago
|
||
https://hg.mozilla.org/mozilla-central/rev/fbd89012eda7
https://hg.mozilla.org/mozilla-central/rev/fcaed7860ea4
https://hg.mozilla.org/mozilla-central/rev/3f5eae4fac0f
https://hg.mozilla.org/mozilla-central/rev/f687743cc499
Status: NEW → RESOLVED
Closed: 10 years ago
status-firefox39:
--- → fixed
Resolution: --- → FIXED
Target Milestone: --- → mozilla39
Assignee | ||
Comment 19•10 years ago
|
||
This followup addresses Timothy's review comment above by removing the duplicate
multiPartChannel variable from imgRequest::OnStartRequest.
Assignee | ||
Comment 20•10 years ago
|
||
Pushed the followup:
https://hg.mozilla.org/integration/mozilla-inbound/rev/8ae8156e0956
Comment 21•10 years ago
|
||
You need to log in
before you can comment on or make changes to this bug.
Description
•