Skip to content

Non-determinism when using InProcessTransport with direct executors #2444

@grandseiken

Description

@grandseiken

What version of gRPC are you using?

1.0.1

What JVM are you using (java -version)?

1.8.0_101

What did you do?

I am attempting to write unit tests for RPC systems without mocking stubs, which would be lovely if it worked. Recent discussions (e.g. here, 1.0.1 release notes, this example) suggest this is the new done thing and is supposed to work. To this end I am running an InProcessServer and calling it over an InProcessChannel with both ends using a DirectExecutor.

What did you expect to see?

The 1.0.1 release notes imply that this is now deterministic (after the removal of the grpc-ready thread pool, which was causing issues for me before), so I expected to see determinism in my unit test, and for the server handler to be called on the same thread that made the stub method call. The RPC method is a client-side streaming RPC, but I don't know if that is relevant.

What did you see instead?

The unit test is not deterministic, since the service handler seems to end up being called on a grpc-timer thread provided by the ScheduledExecutorService created by GrpcUtil.TIMER_SERVICE. The task that causes the call is the NameResolverStartTask in ManagedChannelImpl, and there appears to be no way to provide an alternative timer service.

See ManagedChannelImpl.java line 307.

For reference, the stack trace that I see on the handler side is as follows:

        ...
	at foo.bar.FooServiceImpl$1.onNext(FooServiceImpl.java:33)
	at io.grpc.stub.ServerCalls$2$1.onMessage(ServerCalls.java:206)
	at io.grpc.internal.ServerCallImpl$ServerStreamListenerImpl.messageRead(ServerCallImpl.java:237)
	at io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$1.runInContext(ServerImpl.java:485)
	at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:52)
	at io.grpc.internal.SerializeReentrantCallsDirectExecutor.execute(SerializeReentrantCallsDirectExecutor.java:65)
	at io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener.messageRead(ServerImpl.java:481)
	at io.grpc.inprocess.InProcessTransport$InProcessStream$InProcessClientStream.writeMessage(InProcessTransport.java:475)
	at io.grpc.internal.DelayedStream$3.run(DelayedStream.java:201)
	at io.grpc.internal.DelayedStream.drainPendingCalls(DelayedStream.java:121)
	at io.grpc.internal.DelayedStream.setStream(DelayedStream.java:90)
	at io.grpc.internal.DelayedClientTransport$PendingStream.createRealStream(DelayedClientTransport.java:383)
	at io.grpc.internal.DelayedClientTransport$PendingStream.access$100(DelayedClientTransport.java:370)
	at io.grpc.internal.DelayedClientTransport$2.run(DelayedClientTransport.java:262)
	at com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:456)
	at io.grpc.internal.DelayedClientTransport.setTransportSupplier(DelayedClientTransport.java:259)
	at io.grpc.internal.ManagedChannelImpl$InterimTransportImpl.closeWithRealTransports(ManagedChannelImpl.java:740)
	at io.grpc.DummyLoadBalancerFactory$DummyLoadBalancer.handleResolvedAddresses(DummyLoadBalancerFactory.java:133)
	at io.grpc.internal.ManagedChannelImpl$NameResolverListenerImpl.onUpdate(ManagedChannelImpl.java:681)
	at io.grpc.internal.AbstractManagedChannelImplBuilder$DirectAddressNameResolverFactory$1.start(AbstractManagedChannelImplBuilder.java:323)
	at io.grpc.internal.ManagedChannelImpl$1NameResolverStartTask.run(ManagedChannelImpl.java:254)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

Am I doing something wrong? Is there a workaround? It seems like doing the name resolution work on the ManagedChannelImpl's user-provided executor rather than its scheduledExecutor might fix it, but I don't know if there's some reason it's not done that way.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions