reset to 84f95b9 with history.
diff --git a/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java b/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java
index ba25c8d..a16469e 100644
--- a/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java
+++ b/library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java
@@ -550,47 +550,6 @@
         return into(glide.buildImageViewTarget(view, transcodeClass));
     }
 
-    private Request buildRequest(Target<TranscodeType> target) {
-        final Request result;
-
-        if (priority == null) {
-            priority = Priority.NORMAL;
-        }
-
-        if (thumbnailRequestBuilder != null) {
-            ThumbnailRequestCoordinator requestCoordinator = new ThumbnailRequestCoordinator();
-            Request fullRequest = buildRequest(target, sizeMultiplier, priority, requestCoordinator);
-
-            if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) {
-                thumbnailRequestBuilder.animationFactory = animationFactory;
-            }
-
-            if (thumbnailRequestBuilder.requestListener == null && requestListener != null) {
-                thumbnailRequestBuilder.requestListener = requestListener;
-            }
-
-            if (thumbnailRequestBuilder.priority == null) {
-                thumbnailRequestBuilder.priority = getThumbnailPriority();
-            }
-
-            Request thumbnailRequest = thumbnailRequestBuilder.buildRequest(target,
-                    thumbnailRequestBuilder.sizeMultiplier, thumbnailRequestBuilder.priority, requestCoordinator);
-
-            requestCoordinator.setRequests(fullRequest, thumbnailRequest);
-            result = requestCoordinator;
-        } else if (thumbSizeMultiplier != null) {
-            ThumbnailRequestCoordinator requestCoordinator = new ThumbnailRequestCoordinator();
-            Request fullRequest = buildRequest(target, sizeMultiplier, priority, requestCoordinator);
-            Request thumbnailRequest = buildRequest(target, thumbSizeMultiplier, getThumbnailPriority(),
-                    requestCoordinator);
-            requestCoordinator.setRequests(fullRequest, thumbnailRequest);
-            result = requestCoordinator;
-        } else {
-            result = buildRequest(target, sizeMultiplier, priority, null);
-        }
-        return result;
-    }
-
     private Priority getThumbnailPriority() {
         final Priority result;
         if (priority == Priority.LOW) {
@@ -603,18 +562,53 @@
         return result;
     }
 
-    private Request buildRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
-            RequestCoordinator requestCoordinator) {
-        if (model == null) {
-            return buildRequestForDataType(target, loadProvider, sizeMultiplier, priority, null);
+    private Request buildRequest(Target<TranscodeType> target) {
+        if (priority == null) {
+            priority = Priority.NORMAL;
+        }
+        return buildRequestRecursive(target, null);
+    }
+
+    private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
+        if (thumbnailRequestBuilder != null) {
+            // Recursive case: contains a potentially recursive thumbnail request builder.
+            if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) {
+                thumbnailRequestBuilder.animationFactory = animationFactory;
+            }
+
+            if (thumbnailRequestBuilder.requestListener == null && requestListener != null) {
+                thumbnailRequestBuilder.requestListener = requestListener;
+            }
+
+            if (thumbnailRequestBuilder.priority == null) {
+                thumbnailRequestBuilder.priority = getThumbnailPriority();
+            }
+
+            ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
+            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
+            // Recursively generate thumbnail requests.
+            Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);
+            coordinator.setRequests(fullRequest, thumbRequest);
+            return coordinator;
+        } else if (thumbSizeMultiplier != null) {
+            // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.
+            ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
+            Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);
+            Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);
+            coordinator.setRequests(fullRequest, thumbnailRequest);
+            return coordinator;
         } else {
-            return buildRequestForDataType(target, loadProvider, sizeMultiplier, priority, requestCoordinator);
+            // Base case: no thumbnail.
+            return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
         }
     }
 
-    private <Z> Request buildRequestForDataType(Target<TranscodeType> target,
-            LoadProvider<ModelType, Z, ResourceType, TranscodeType> loadProvider, float sizeMultiplier,
-            Priority priority, RequestCoordinator requestCoordinator) {
+    private <Z> Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
+            RequestCoordinator requestCoordinator) {
+        if (model == null) {
+            requestCoordinator = null;
+        }
+
         return GenericRequest.obtain(
                 loadProvider,
                 model,
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java
index 2714b6a..9cffbd8 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java
@@ -79,20 +79,41 @@
      */
     public static Bitmap fitCenter(Bitmap toFit, BitmapPool pool, int width, int height) {
         if (toFit.getWidth() == width && toFit.getHeight() == height) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "requested target size matches input, returning input");
+            }
             return toFit;
         }
         final float widthPercentage = width / (float) toFit.getWidth();
         final float heightPercentage = height / (float) toFit.getHeight();
         final float minPercentage = Math.min(widthPercentage, heightPercentage);
 
-        final int targetWidth = Math.round(minPercentage * toFit.getWidth());
-        final int targetHeight = Math.round(minPercentage * toFit.getHeight());
+        // take the floor of the target width/height, not round. If the matrix
+        // passed into drawBitmap rounds differently, we want to slightly
+        // overdraw, not underdraw, to avoid artifacts from bitmap reuse.
+        final int targetWidth = (int) (minPercentage * toFit.getWidth());
+        final int targetHeight = (int) (minPercentage * toFit.getHeight());
+
+        if (toFit.getWidth() == targetWidth && toFit.getHeight() == targetHeight) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "adjusted target size matches input, returning input");
+            }
+            return toFit;
+        }
 
         Bitmap.Config config = toFit.getConfig() != null ? toFit.getConfig() : Bitmap.Config.ARGB_8888;
         Bitmap toReuse = pool.get(targetWidth, targetHeight, config);
         if (toReuse == null) {
             toReuse = Bitmap.createBitmap(targetWidth, targetHeight, config);
         }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "request: " + width + "x" + height);
+            Log.v(TAG, "toFit:   " + toFit.getWidth() + "x" + toFit.getHeight());
+            Log.v(TAG, "toReuse: " + toReuse.getWidth() + "x" + toReuse.getHeight());
+            Log.v(TAG, "minPct:   " + minPercentage);
+        }
+
         Canvas canvas = new Canvas(toReuse);
         Matrix matrix = new Matrix();
         matrix.setScale(minPercentage, minPercentage);
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java b/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java
index 4dd76e3..83acb5c 100644
--- a/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java
+++ b/library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java
@@ -70,8 +70,11 @@
         if (decodedClass.equals(transcodedClass)) {
             return UnitTranscoder.get();
         }
-        GET_KEY.set(decodedClass, transcodedClass);
-        ResourceTranscoder<Z, R> result = factories.get(GET_KEY);
+        ResourceTranscoder<Z, R> result;
+        synchronized (GET_KEY) {
+            GET_KEY.set(decodedClass, transcodedClass);
+            result = factories.get(GET_KEY);
+        }
         if (result == null) {
             throw new IllegalArgumentException("No transcoder registered for " + decodedClass + " and "
                     + transcodedClass);
diff --git a/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java b/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java
index c2260a7..21bd4b4 100644
--- a/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java
+++ b/library/src/main/java/com/bumptech/glide/manager/RequestManagerFragment.java
@@ -2,11 +2,14 @@
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
+import android.util.Log;
+
 import com.bumptech.glide.RequestManager;
 
 @TargetApi(11)
 public class RequestManagerFragment extends Fragment {
     private RequestManager requestManager;
+    private static String TAG = "RequestManagerFragment";
 
     public void setRequestManager(RequestManager requestManager) {
         this.requestManager = requestManager;
@@ -28,7 +31,11 @@
     public void onStop() {
         super.onStop();
         if (requestManager != null) {
-            requestManager.onStop();
+            try {
+                requestManager.onStop();
+            } catch (RuntimeException e) {
+                Log.e(TAG, "exception during onStop", e);
+            }
         }
     }
 
@@ -36,7 +43,11 @@
     public void onDestroy() {
         super.onDestroy();
         if (requestManager != null) {
-            requestManager.onDestroy();
+            try {
+                requestManager.onDestroy();
+            } catch (RuntimeException e) {
+                Log.e(TAG, "exception during onDestroy", e);
+            }
         }
     }
 }
diff --git a/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java b/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java
index 019b397..63d22a3 100644
--- a/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java
+++ b/library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java
@@ -69,8 +69,11 @@
 
     @SuppressWarnings("unchecked")
     public <T, Z> DataLoadProvider<T, Z> get(Class<T> dataClass, Class<Z> resourceClass) {
-        GET_KEY.set(dataClass, resourceClass);
-        DataLoadProvider<T, Z> result = providers.get(GET_KEY);
+        DataLoadProvider<T, Z> result;
+        synchronized (GET_KEY) {
+            GET_KEY.set(dataClass, resourceClass);
+            result = providers.get(GET_KEY);
+        }
         if (result == null) {
             result = EmptyDataLoadProvider.get();
         }
diff --git a/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java b/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java
index 319211b..d5ec86c 100644
--- a/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java
+++ b/library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java
@@ -7,6 +7,15 @@
 public class ThumbnailRequestCoordinator implements RequestCoordinator, Request {
     private Request full;
     private Request thumb;
+    private RequestCoordinator coordinator;
+
+    public ThumbnailRequestCoordinator() {
+        this(null);
+    }
+
+    public ThumbnailRequestCoordinator(RequestCoordinator coordinator) {
+        this.coordinator = coordinator;
+    }
 
     public void setRequests(Request full, Request thumb) {
         this.full = full;
@@ -15,17 +24,29 @@
 
     @Override
     public boolean canSetImage(Request request) {
-        return request == full || !full.isComplete();
+        return parentCanSetImage() && (request == full || !full.isComplete());
+    }
+
+    private boolean parentCanSetImage() {
+        return coordinator == null || coordinator.canSetImage(this);
     }
 
     @Override
     public boolean canSetPlaceholder(Request request) {
-        return request == full && !isAnyRequestComplete();
+        return parentCanSetPlaceholder() && (request == full && !isAnyRequestComplete());
+    }
+
+    private boolean parentCanSetPlaceholder() {
+        return coordinator == null || coordinator.canSetPlaceholder(this);
     }
 
     @Override
     public boolean isAnyRequestComplete() {
-        return full.isComplete() || thumb.isComplete();
+        return parentIsAnyRequestComplete() || full.isComplete() || thumb.isComplete();
+    }
+
+    private boolean parentIsAnyRequestComplete() {
+        return coordinator != null && coordinator.isAnyRequestComplete();
     }
 
     @Override
@@ -51,7 +72,9 @@
 
     @Override
     public boolean isComplete() {
-        return full.isComplete();
+        // TODO: this is a little strange, but we often want to avoid restarting the request or
+        // setting placeholders even if only the thumb is complete.
+        return full.isComplete() || thumb.isComplete();
     }
 
     @Override