Skip to content

Commit 4de2cda

Browse files
sjuddglide-copybara-robot
authored andcommittedJul 30, 2019
Avoid holding locks when releasing resources.
We need to lock while updating the various states of the objects that care about resources. We don't need a lock when all we're doing is releasing the resource itself. Previously we fixed a deadlock by adding locks so that locks were always acquired in order. It appears though that we can go the other direction for this particular case and avoid holding any locks, which should also solve the deadlock. If it turns out this doesn't work we can revert this and add another layer of locking when releasing from the memory cache. Ideally though this should provide better performance with too much more complication. Fixes #3795 PiperOrigin-RevId: 260748170
1 parent a0ddfc9 commit 4de2cda

File tree

5 files changed

+111
-100
lines changed

5 files changed

+111
-100
lines changed
 

‎library/src/main/java/com/bumptech/glide/load/engine/ActiveResources.java

+9-16
Original file line numberDiff line numberDiff line change
@@ -106,25 +106,18 @@ synchronized EngineResource<?> get(Key key) {
106106
@SuppressWarnings({"WeakerAccess", "SynchronizeOnNonFinalField"})
107107
@Synthetic
108108
void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
109-
// Fixes a deadlock where we normally acquire the Engine lock and then the ActiveResources lock
110-
// but reverse that order in this one particular test. This is definitely a bit of a hack...
111-
synchronized (listener) {
112-
synchronized (this) {
113-
activeEngineResources.remove(ref.key);
109+
synchronized (this) {
110+
activeEngineResources.remove(ref.key);
114111

115-
if (!ref.isCacheable || ref.resource == null) {
116-
return;
117-
}
118-
EngineResource<?> newResource =
119-
new EngineResource<>(
120-
ref.resource,
121-
/*isMemoryCacheable=*/ true,
122-
/*isRecyclable=*/ false,
123-
ref.key,
124-
listener);
125-
listener.onResourceReleased(ref.key, newResource);
112+
if (!ref.isCacheable || ref.resource == null) {
113+
return;
126114
}
127115
}
116+
117+
EngineResource<?> newResource =
118+
new EngineResource<>(
119+
ref.resource, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ false, ref.key, listener);
120+
listener.onResourceReleased(ref.key, newResource);
128121
}
129122

130123
@SuppressWarnings("WeakerAccess")

‎library/src/main/java/com/bumptech/glide/load/engine/Engine.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ public void onResourceRemoved(@NonNull final Resource<?> resource) {
388388
}
389389

390390
@Override
391-
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
391+
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
392392
activeResources.deactivate(cacheKey);
393393
if (resource.isMemoryCacheable()) {
394394
cache.put(cacheKey, resource);

‎library/src/main/java/com/bumptech/glide/load/engine/EngineJob.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -274,17 +274,22 @@ synchronized void incrementPendingCallbacks(int count) {
274274

275275
@SuppressWarnings("WeakerAccess")
276276
@Synthetic
277-
synchronized void decrementPendingCallbacks() {
278-
stateVerifier.throwIfRecycled();
279-
Preconditions.checkArgument(isDone(), "Not yet complete!");
280-
int decremented = pendingCallbacks.decrementAndGet();
281-
Preconditions.checkArgument(decremented >= 0, "Can't decrement below 0");
282-
if (decremented == 0) {
283-
if (engineResource != null) {
284-
engineResource.release();
277+
void decrementPendingCallbacks() {
278+
EngineResource<?> toRelease = null;
279+
synchronized (this) {
280+
stateVerifier.throwIfRecycled();
281+
Preconditions.checkArgument(isDone(), "Not yet complete!");
282+
int decremented = pendingCallbacks.decrementAndGet();
283+
Preconditions.checkArgument(decremented >= 0, "Can't decrement below 0");
284+
if (decremented == 0) {
285+
toRelease = engineResource;
286+
287+
release();
285288
}
289+
}
286290

287-
release();
291+
if (toRelease != null) {
292+
toRelease.release();
288293
}
289294
}
290295

‎library/src/main/java/com/bumptech/glide/load/engine/EngineResource.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,17 @@ synchronized void acquire() {
103103
// listener is effectively final.
104104
@SuppressWarnings("SynchronizeOnNonFinalField")
105105
void release() {
106-
// To avoid deadlock, always acquire the listener lock before our lock so that the locking
107-
// scheme is consistent (Engine -> EngineResource). Violating this order leads to deadlock
108-
// (b/123646037).
109-
synchronized (listener) {
110-
synchronized (this) {
111-
if (acquired <= 0) {
112-
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
113-
}
114-
if (--acquired == 0) {
115-
listener.onResourceReleased(key, this);
116-
}
106+
boolean release = false;
107+
synchronized (this) {
108+
if (acquired <= 0) {
109+
throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
117110
}
111+
if (--acquired == 0) {
112+
release = true;
113+
}
114+
}
115+
if (release) {
116+
listener.onResourceReleased(key, this);
118117
}
119118
}
120119

‎library/src/main/java/com/bumptech/glide/request/SingleRequest.java

+77-63
Original file line numberDiff line numberDiff line change
@@ -307,22 +307,30 @@ private void assertNotCallingCallbacks() {
307307
* @see #cancel()
308308
*/
309309
@Override
310-
public synchronized void clear() {
311-
assertNotCallingCallbacks();
312-
stateVerifier.throwIfRecycled();
313-
if (status == Status.CLEARED) {
314-
return;
315-
}
316-
cancel();
317-
// Resource must be released before canNotifyStatusChanged is called.
318-
if (resource != null) {
319-
releaseResource(resource);
320-
}
321-
if (canNotifyCleared()) {
322-
target.onLoadCleared(getPlaceholderDrawable());
310+
public void clear() {
311+
Resource<R> toRelease = null;
312+
synchronized (this) {
313+
assertNotCallingCallbacks();
314+
stateVerifier.throwIfRecycled();
315+
if (status == Status.CLEARED) {
316+
return;
317+
}
318+
cancel();
319+
// Resource must be released before canNotifyStatusChanged is called.
320+
if (resource != null) {
321+
toRelease = resource;
322+
resource = null;
323+
}
324+
if (canNotifyCleared()) {
325+
target.onLoadCleared(getPlaceholderDrawable());
326+
}
327+
328+
status = Status.CLEARED;
323329
}
324330

325-
status = Status.CLEARED;
331+
if (toRelease != null) {
332+
engine.release(toRelease);
333+
}
326334
}
327335

328336
@Override
@@ -332,11 +340,6 @@ public synchronized void pause() {
332340
}
333341
}
334342

335-
private void releaseResource(Resource<?> resource) {
336-
engine.release(resource);
337-
this.resource = null;
338-
}
339-
340343
@Override
341344
public synchronized boolean isRunning() {
342345
return status == Status.RUNNING || status == Status.WAITING_FOR_SIZE;
@@ -506,53 +509,64 @@ private void notifyLoadFailed() {
506509
@SuppressWarnings("unchecked")
507510
@Override
508511
public synchronized void onResourceReady(Resource<?> resource, DataSource dataSource) {
509-
stateVerifier.throwIfRecycled();
510-
loadStatus = null;
511-
if (resource == null) {
512-
GlideException exception =
513-
new GlideException(
514-
"Expected to receive a Resource<R> with an "
515-
+ "object of "
516-
+ transcodeClass
517-
+ " inside, but instead got null.");
518-
onLoadFailed(exception);
519-
return;
520-
}
512+
Resource<?> toRelease = null;
513+
try {
514+
synchronized (this) {
515+
stateVerifier.throwIfRecycled();
516+
loadStatus = null;
517+
if (resource == null) {
518+
GlideException exception =
519+
new GlideException(
520+
"Expected to receive a Resource<R> with an "
521+
+ "object of "
522+
+ transcodeClass
523+
+ " inside, but instead got null.");
524+
onLoadFailed(exception);
525+
return;
526+
}
521527

522-
Object received = resource.get();
523-
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
524-
releaseResource(resource);
525-
GlideException exception =
526-
new GlideException(
527-
"Expected to receive an object of "
528-
+ transcodeClass
529-
+ " but instead"
530-
+ " got "
531-
+ (received != null ? received.getClass() : "")
532-
+ "{"
533-
+ received
534-
+ "} inside"
535-
+ " "
536-
+ "Resource{"
537-
+ resource
538-
+ "}."
539-
+ (received != null
540-
? ""
541-
: " "
542-
+ "To indicate failure return a null Resource "
543-
+ "object, rather than a Resource object containing null data."));
544-
onLoadFailed(exception);
545-
return;
546-
}
528+
Object received = resource.get();
529+
if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
530+
toRelease = resource;
531+
this.resource = null;
532+
GlideException exception =
533+
new GlideException(
534+
"Expected to receive an object of "
535+
+ transcodeClass
536+
+ " but instead"
537+
+ " got "
538+
+ (received != null ? received.getClass() : "")
539+
+ "{"
540+
+ received
541+
+ "} inside"
542+
+ " "
543+
+ "Resource{"
544+
+ resource
545+
+ "}."
546+
+ (received != null
547+
? ""
548+
: " "
549+
+ "To indicate failure return a null Resource "
550+
+ "object, rather than a Resource object containing null data."));
551+
onLoadFailed(exception);
552+
return;
553+
}
547554

548-
if (!canSetResource()) {
549-
releaseResource(resource);
550-
// We can't put the status to complete before asking canSetResource().
551-
status = Status.COMPLETE;
552-
return;
553-
}
555+
if (!canSetResource()) {
556+
toRelease = resource;
557+
this.resource = null;
558+
// We can't put the status to complete before asking canSetResource().
559+
status = Status.COMPLETE;
560+
return;
561+
}
554562

555-
onResourceReady((Resource<R>) resource, (R) received, dataSource);
563+
onResourceReady((Resource<R>) resource, (R) received, dataSource);
564+
}
565+
} finally {
566+
if (toRelease != null) {
567+
engine.release(toRelease);
568+
}
569+
}
556570
}
557571

558572
/**

0 commit comments

Comments
 (0)
Please sign in to comment.