master moved from 4daa205dc4fe to 4a4d8257ed41

2 new revisions:

Revision: 409e0f578b62
Author:   Sam Berlin <[email protected]>
Date:     Sat May 10 14:34:16 2014 UTC
Log: Try to use cglibs FastClass to invoke @Provides methods, it's faster!...
http://code.google.com/p/google-guice/source/detail?r=409e0f578b62

Revision: 4a4d8257ed41
Author:   Sam Berlin <[email protected]>
Date:     Sat May 10 14:34:44 2014 UTC
Log: Block when transferring request scope instead of checking owners & thr...
http://code.google.com/p/google-guice/source/detail?r=4a4d8257ed41

==============================================================================
Revision: 409e0f578b62
Author:   Sam Berlin <[email protected]>
Date:     Sat May 10 14:34:16 2014 UTC
Log: Try to use cglibs FastClass to invoke @Provides methods, it's faster!

Here is a caliper benchmark to demonstrate the difference.
https://caliper.googleplex.com/runs/2d349fec-2742-45e1-b6e5-16997c522387#r:scenario.benchmarkSpec.methodName,scenario.benchmarkSpec.parameters.strategy

This should save about 200-300 ns per method invocation and about 224 bytes (over 4 objects) of allocations for each invocation.

The cost of this (of course) is greater permgen usage and potentially slower startup/injector creation due to the class generation.
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=66364901

http://code.google.com/p/google-guice/source/detail?r=409e0f578b62

Modified:
 /core/src/com/google/inject/internal/ProviderMethod.java
 /core/src/com/google/inject/internal/ProviderMethodsModule.java
 /core/test/com/google/inject/spi/ProviderMethodsTest.java

=======================================
--- /core/src/com/google/inject/internal/ProviderMethod.java Mon Mar 10 16:52:01 2014 UTC +++ /core/src/com/google/inject/internal/ProviderMethod.java Sat May 10 14:34:16 2014 UTC
@@ -17,12 +17,14 @@
 package com.google.inject.internal;

 import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.Binder;
 import com.google.inject.Exposed;
 import com.google.inject.Key;
 import com.google.inject.PrivateBinder;
 import com.google.inject.Provider;
+import com.google.inject.internal.BytecodeGen.Visibility;
 import com.google.inject.internal.util.StackTraceElements;
 import com.google.inject.spi.BindingTargetVisitor;
 import com.google.inject.spi.Dependency;
@@ -35,6 +37,7 @@
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.List;
 import java.util.Set;

@@ -43,12 +46,44 @@
  *
  * @author [email protected] (Jesse Wilson)
  */
-public class ProviderMethod<T> implements ProviderWithExtensionVisitor<T>, HasDependencies, +public abstract class ProviderMethod<T> implements ProviderWithExtensionVisitor<T>, HasDependencies,
     ProvidesMethodBinding<T> {
+
+  /**
+   * Creates a {@link ProviderMethod}.
+   *
+ * <p>Ideally, we will use {@link FastClass} to invoke the actual method, since it is + * significantly faster. However, this will fail if the method is {@code private} or
+   * {@code protected}, since fastclass is subject to java access policies.
+   */
+ static <T> ProviderMethod<T> create(Key<T> key, Method method, Object instance, + ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders,
+      Class<? extends Annotation> scopeAnnotation) {
+    int modifiers = method.getModifiers();
+    /*if[AOP]*/
+ if (!Modifier.isPrivate(modifiers) && !Modifier.isProtected(modifiers)) {
+      try {
+        // We use an index instead of FastMethod to save a stack frame.
+        return new FastClassProviderMethod<T>(
+ key, method, instance, dependencies, parameterProviders, scopeAnnotation); + } catch (net.sf.cglib.core.CodeGenerationException e) {/* fall-through */}
+    }
+    /*end[AOP]*/
+
+    if (!Modifier.isPublic(modifiers) ||
+        !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+      method.setAccessible(true);
+    }
+
+    return new ReflectionProviderMethod<T>(
+ key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+  }
+
+  protected final Object instance;
+  protected final Method method;
+
   private final Key<T> key;
   private final Class<? extends Annotation> scopeAnnotation;
-  private final Object instance;
-  private final Method method;
   private final ImmutableSet<Dependency<?>> dependencies;
   private final List<Provider<?>> parameterProviders;
   private final boolean exposed;
@@ -56,7 +91,7 @@
   /**
* @param method the method to invoke. It's return type must be the same type as {@code key}.
    */
-  ProviderMethod(Key<T> key, Method method, Object instance,
+  private ProviderMethod(Key<T> key, Method method, Object instance,
ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders,
       Class<? extends Annotation> scopeAnnotation) {
     this.key = key;
@@ -66,8 +101,6 @@
     this.method = method;
     this.parameterProviders = parameterProviders;
     this.exposed = method.isAnnotationPresent(Exposed.class);
-
-    method.setAccessible(true);
   }

   public Key<T> getKey() {
@@ -110,9 +143,8 @@
     }

     try {
-      // We know this cast is safe becase T is the method's return type.
       @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" })
-      T result = (T) method.invoke(instance, parameters);
+      T result = (T) doProvision(parameters);
       return result;
     } catch (IllegalAccessException e) {
       throw new AssertionError(e);
@@ -120,6 +152,10 @@
       throw Exceptions.rethrowCause(e);
     }
   }
+
+ /** Extension point for our subclasses to implement the provisioning strategy. */
+  abstract Object doProvision(Object[] parameters)
+      throws IllegalAccessException, InvocationTargetException;

   public Set<Dependency<?>> getDependencies() {
     return dependencies;
@@ -156,4 +192,61 @@
     // (We need to call equals, so we do.  But we can avoid hashCode.)
     return Objects.hashCode(method);
   }
+
+  /*if[AOP]*/
+  /**
+ * A {@link ProviderMethod} implementation that uses {@link net.sf.cglib.reflect.FastClass#invoke}
+   * to invoke the provider method.
+   */
+ private static final class FastClassProviderMethod<T> extends ProviderMethod<T> {
+    final net.sf.cglib.reflect.FastClass fastClass;
+    final int methodIndex;
+
+    FastClassProviderMethod(Key<T> key,
+        Method method,
+        Object instance,
+        ImmutableSet<Dependency<?>> dependencies,
+        List<Provider<?>> parameterProviders,
+        Class<? extends Annotation> scopeAnnotation) {
+ super(key, method, instance, dependencies, parameterProviders, scopeAnnotation); + // We need to generate a FastClass for the method's class, not the object's class.
+      this.fastClass =
+ BytecodeGen.newFastClass(method.getDeclaringClass(), Visibility.forMember(method)); + // Use the Signature overload of getIndex because it properly uses return types to identify + // particular methods. This is normally irrelevant, except in the case of covariant overrides + // which java implements with a compiler generated bridge method to implement the override.
+      this.methodIndex = fastClass.getIndex(
+          new net.sf.cglib.core.Signature(
+ method.getName(), org.objectweb.asm.Type.getMethodDescriptor(method)));
+      Preconditions.checkArgument(this.methodIndex >= 0,
+          "Could not find method %s in fast class for class %s",
+          method,
+          method.getDeclaringClass());
+    }
+
+    @Override public Object doProvision(Object[] parameters)
+        throws IllegalAccessException, InvocationTargetException {
+      return fastClass.invoke(methodIndex, instance, parameters);
+    }
+  }
+  /*end[AOP]*/
+
+  /**
+ * A {@link ProviderMethod} implementation that invokes the method using normal java reflection.
+   */
+ private static final class ReflectionProviderMethod<T> extends ProviderMethod<T> {
+    ReflectionProviderMethod(Key<T> key,
+        Method method,
+        Object instance,
+        ImmutableSet<Dependency<?>> dependencies,
+        List<Provider<?>> parameterProviders,
+        Class<? extends Annotation> scopeAnnotation) {
+ super(key, method, instance, dependencies, parameterProviders, scopeAnnotation);
+    }
+
+ @Override Object doProvision(Object[] parameters) throws IllegalAccessException,
+        InvocationTargetException {
+      return method.invoke(instance, parameters);
+    }
+  }
 }
=======================================
--- /core/src/com/google/inject/internal/ProviderMethodsModule.java Fri Jul 8 00:34:16 2011 UTC +++ /core/src/com/google/inject/internal/ProviderMethodsModule.java Sat May 10 14:34:16 2014 UTC
@@ -123,7 +123,7 @@
       binder.addError(message);
     }

- return new ProviderMethod<T>(key, method, delegate, ImmutableSet.copyOf(dependencies), + return ProviderMethod.create(key, method, delegate, ImmutableSet.copyOf(dependencies),
         parameterProviders, scopeAnnotation);
   }

=======================================
--- /core/test/com/google/inject/spi/ProviderMethodsTest.java Mon Mar 10 16:52:01 2014 UTC +++ /core/test/com/google/inject/spi/ProviderMethodsTest.java Sat May 10 14:34:16 2014 UTC
@@ -33,6 +33,8 @@
 import com.google.inject.Module;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
+import com.google.inject.Stage;
+import com.google.inject.internal.InternalFlags;
 import com.google.inject.internal.ProviderMethod;
 import com.google.inject.internal.ProviderMethodsModule;
 import com.google.inject.name.Named;
@@ -44,6 +46,7 @@
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
@@ -355,7 +358,7 @@

     ProviderInstanceBinding binding = (ProviderInstanceBinding) element;
     javax.inject.Provider provider = binding.getUserSuppliedProvider();
-    assertEquals(ProviderMethod.class, provider.getClass());
+    assertTrue(ProviderMethod.class.isAssignableFrom(provider.getClass()));
     assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
     assertSame(provider, binding.getProviderInstance());
   }
@@ -452,4 +455,192 @@
       throw new IllegalStateException("unexpected visit of: " + binding);
     }
   }
+
+  public void testProvidesMethodVisibility() {
+    Injector injector = Guice.createInjector(new VisibilityModule());
+
+    assertEquals(42, injector.getInstance(Integer.class).intValue());
+    assertEquals(42L, injector.getInstance(Long.class).longValue());
+    assertEquals(42D, injector.getInstance(Double.class).doubleValue());
+    assertEquals(42F, injector.getInstance(Float.class).floatValue());
+  }
+
+  private static class VisibilityModule extends AbstractModule {
+    @Override protected void configure() {}
+
+    @SuppressWarnings("unused")
+    @Provides Integer foo() {
+      return 42;
+    }
+
+    @SuppressWarnings("unused")
+    @Provides private Long bar() {
+      return 42L;
+    }
+
+    @SuppressWarnings("unused")
+    @Provides protected Double baz() {
+      return 42D;
+    }
+
+    @SuppressWarnings("unused")
+    @Provides public Float quux() {
+      return 42F;
+    }
+  }
+
+  public void testProvidesMethodInheritenceHierarchy() {
+    try {
+      Guice.createInjector(new Sub1Module(), new Sub2Module());
+      fail("Expected injector creation failure");
+    } catch (CreationException expected) {
+      // both of our super class bindings cause errors
+      assertContains(expected.getMessage(),
+          "A binding to java.lang.Long was already configured",
+          "A binding to java.lang.Integer was already configured");
+    }
+  }
+
+  public void testProvidesMethodsDefinedInSuperClass() {
+    Injector injector = Guice.createInjector(new Sub1Module());
+    assertEquals(42, injector.getInstance(Integer.class).intValue());
+    assertEquals(42L, injector.getInstance(Long.class).longValue());
+    assertEquals(42D, injector.getInstance(Double.class).doubleValue());
+  }
+
+  private static class BaseModule extends AbstractModule {
+    @Override protected void configure() {}
+
+    @Provides Integer foo() {
+      return 42;
+    }
+
+    @Provides Long bar() {
+      return 42L;
+    }
+  }
+
+  private static class Sub1Module extends BaseModule {
+    @Provides Double baz() {
+      return 42D;
+    }
+  }
+
+  private static class Sub2Module extends BaseModule {
+    @Provides Float quux() {
+      return 42F;
+    }
+  }
+
+  /*if[AOP]*/
+  public void testShareFastClass() {
+    CallerInspecterModule module = new CallerInspecterModule();
+    Guice.createInjector(Stage.PRODUCTION, module);
+    assertEquals(module.fooCallerClass, module.barCallerClass);
+    assertTrue(module.fooCallerClass.contains("$$FastClassByGuice$$"));
+  }
+
+  private static class CallerInspecterModule extends AbstractModule {
+    // start them off as unequal
+    String barCallerClass = "not_set_bar";
+    String fooCallerClass = "not_set_foo";
+
+    @Override protected void configure() {}
+
+    @Provides @Singleton Integer foo() {
+ this.fooCallerClass = new Exception().getStackTrace()[1].getClassName();
+      return 42;
+    }
+
+    @Provides @Singleton Long bar() {
+ this.barCallerClass = new Exception().getStackTrace()[1].getClassName();
+      return 42L;
+    }
+  }
+
+  public void testShareFastClassWithSuperClass() {
+ CallerInspecterSubClassModule module = new CallerInspecterSubClassModule();
+    Guice.createInjector(Stage.PRODUCTION, module);
+ assertEquals("Expected provider methods in the same class to share fastclass classes",
+        module.fooCallerClass, module.barCallerClass);
+    assertFalse(
+ "Did not expect provider methods in the subclasses to share fastclass classes "
+            + "with their parent classes",
+        module.bazCallerClass.equals(module.barCallerClass));
+  }
+
+
+ private static class CallerInspecterSubClassModule extends CallerInspecterModule {
+    String bazCallerClass;
+
+    @Override protected void configure() {}
+
+    @Provides @Singleton Double baz() {
+ this.bazCallerClass = new Exception().getStackTrace()[1].getClassName();
+      return 42D;
+    }
+  }
+  /*end[AOP]*/
+
+  // Test the behavior of provider methods when they are overridden
+  public void testOverrideProviderMethod() {
+    try {
+      Guice.createInjector(new SuperClassModule() {
+        @Override
+        @Provides
+        Double normalOverrideWithProvides() {
+          return 2D;
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+      assertContains(expected.getMessage(),
+          "A binding to java.lang.Double was already configured");
+    }
+
+ Injector injector = Guice.createInjector(Stage.PRODUCTION, new SuperClassModule() {
+      @Override Long normalOverrideWithoutProvides() {
+        return 2L;
+      }
+    });
+    assertEquals(2L, injector.getInstance(Long.class).longValue());
+
+ injector = Guice.createInjector(Stage.PRODUCTION, new SuperClassModule() { + @Override ImmutableSet<String> covariantReturnOverrideWithoutProvides() {
+        return ImmutableSet.of("subset");
+      }
+    });
+    assertEquals(ImmutableSet.of("subset"),
+        injector.getInstance(new Key<Collection<String>>() {}));
+    // This is super weird since two keys get bound to the same method.
+    // TODO(sameb): make this throw an exception at configure() time!
+ injector = Guice.createInjector(Stage.PRODUCTION, new SuperClassModule() {
+      @Override
+      @Provides
+      String covariantReturnOverrideWithProvides() {
+        return "sub";
+      }
+    });
+    assertEquals("sub", injector.getInstance(String.class));
+    assertEquals("sub", injector.getInstance(CharSequence.class));
+  }
+
+  private static class SuperClassModule extends AbstractModule {
+    @Override protected void configure() {}
+    @Provides Double normalOverrideWithProvides() {
+      return 1D;
+    }
+
+    @Provides Long normalOverrideWithoutProvides() {
+      return 1L;
+    }
+
+    @Provides CharSequence covariantReturnOverrideWithProvides() {
+      return "base";
+    }
+
+    @Provides Collection<String> covariantReturnOverrideWithoutProvides() {
+      return ImmutableList.of("baselist");
+    }
+  }
 }

==============================================================================
Revision: 4a4d8257ed41
Author:   Sam Berlin <[email protected]>
Date:     Sat May 10 14:34:44 2014 UTC
Log: Block when transferring request scope instead of checking owners & throwing exceptions. This lets users delete hacky spin loops.

-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=66541958

http://code.google.com/p/google-guice/source/detail?r=4a4d8257ed41

Modified:
 /extensions/servlet/src/com/google/inject/servlet/GuiceFilter.java
 /extensions/servlet/src/com/google/inject/servlet/ServletScopes.java
/extensions/servlet/test/com/google/inject/servlet/TransferRequestIntegrationTest.java

=======================================
--- /extensions/servlet/src/com/google/inject/servlet/GuiceFilter.java Mon Mar 10 16:50:34 2014 UTC +++ /extensions/servlet/src/com/google/inject/servlet/GuiceFilter.java Sat May 10 14:34:44 2014 UTC
@@ -16,7 +16,6 @@

 package com.google.inject.servlet;

-import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
 import com.google.inject.Inject;
 import com.google.inject.Key;
@@ -175,7 +174,6 @@
     final HttpServletRequest originalRequest;
     final HttpServletRequest request;
     final HttpServletResponse response;
-    volatile Thread owner;

     Context(HttpServletRequest originalRequest, HttpServletRequest request,
         HttpServletResponse response) {
@@ -196,18 +194,14 @@
       return response;
     }

-    <T> T call(Callable<T> callable) throws Exception {
-      Thread oldOwner = owner;
-      Thread newOwner = Thread.currentThread();
-      Preconditions.checkState(oldOwner == null || oldOwner == newOwner,
- "Trying to transfer request scope but original scope is still active");
-      owner = newOwner;
+    // Synchronized to prevent two threads from using the same request
+    // scope concurrently.
+    synchronized <T> T call(Callable<T> callable) throws Exception {
       Context previous = localContext.get();
       localContext.set(this);
       try {
         return callable.call();
       } finally {
-        owner = oldOwner;
         localContext.set(previous);
       }
     }
=======================================
--- /extensions/servlet/src/com/google/inject/servlet/ServletScopes.java Mon Mar 10 16:50:34 2014 UTC +++ /extensions/servlet/src/com/google/inject/servlet/ServletScopes.java Sat May 10 14:34:44 2014 UTC
@@ -244,15 +244,10 @@
* where you can detach the request processing thread while waiting for data, * and reattach to a different thread to finish processing at a later time.
    *
-   * <p>Because {@code HttpServletRequest} objects are not typically
-   * thread-safe, the callable returned by this method must not be run on a
- * different thread until the current request scope has terminated. In other - * words, do not use this method to propagate the current request scope to
-   * worker threads that may run concurrently with the current thread.
-   *
- * <p>The returned callable will throw a {@link ScopingException} when called
-   * if the request scope being transferred is still active on a different
-   * thread.
+   * <p>Because request-scoped objects are not typically thread-safe, the
+   * callable returned by this method must not be run on a different thread
+ * until the current request scope has terminated. The returned callable will
+   * block until the current thread has released the request scope.
    *
* @param callable code to be executed in another thread, which depends on
    *     the request scope.
@@ -366,20 +361,15 @@

   private static class Context {
     final Map<Key, Object> map = Maps.newHashMap();
-    volatile Thread owner;

-    <T> T call(Callable<T> callable) throws Exception {
-      Thread oldOwner = owner;
-      Thread newOwner = Thread.currentThread();
-      checkScopingState(oldOwner == null || oldOwner == newOwner,
- "Trying to transfer request scope but original scope is still active");
-      owner = newOwner;
+    // Synchronized to prevent two threads from using the same request
+    // scope concurrently.
+    synchronized <T> T call(Callable<T> callable) throws Exception {
       Context previous = requestScopeContext.get();
       requestScopeContext.set(this);
       try {
         return callable.call();
       } finally {
-        owner = oldOwner;
         requestScopeContext.set(previous);
       }
     }
=======================================
--- /extensions/servlet/test/com/google/inject/servlet/TransferRequestIntegrationTest.java Sun May 27 17:39:27 2012 UTC +++ /extensions/servlet/test/com/google/inject/servlet/TransferRequestIntegrationTest.java Sat May 10 14:34:44 2014 UTC
@@ -27,10 +27,11 @@
 import junit.framework.TestCase;

 import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;

 // TODO: Add test for HTTP transferring.
 /**
@@ -88,16 +89,16 @@
     executor.shutdownNow();
   }

- public void testTransferNonHttpRequest_concurrentUseFails() throws Exception { + public void testTransferNonHttpRequest_concurrentUseBlocks() throws Exception {
     Callable<Boolean> callable = new Callable<Boolean>() {
       @Override public Boolean call() throws Exception {
         ExecutorService executor = Executors.newSingleThreadExecutor();
         try {
Future<Boolean> future = executor.submit(ServletScopes.transferRequest(FALSE_CALLABLE));
           try {
-            return future.get();
-          } catch (ExecutionException e) {
-            return e.getCause() instanceof IllegalStateException;
+            return future.get(100, TimeUnit.MILLISECONDS);
+          } catch (TimeoutException e) {
+            return true;
           }
         } finally {
           executor.shutdownNow();

--
You received this message because you are subscribed to the Google Groups 
"google-guice-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/google-guice-dev.
For more options, visit https://groups.google.com/d/optout.

Reply via email to