Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix "method too large" error #837

Merged
merged 2 commits into from
Mar 11, 2020
Merged

Fix "method too large" error #837

merged 2 commits into from
Mar 11, 2020

Conversation

cb372
Copy link
Member

@cb372 cb372 commented Mar 11, 2020

Refactors the macro-generated code to avoid generating giant method bodies.

Before the fix, the @service macro would generate the following code for the bindService method:

def bindService(...) =
  higherkindness.mu.rpc.internal.service.GRPCServiceDefBuilder.build[F](
    "fully.qualified.ServiceName",
    (FooMethodDescriptor, io.grpc.stub.ServerCalls.asyncUnaryCall(higherkindness.mu.rpc.internal.server.unaryCalls.unaryMethod(algebra.foo, None))),
    (BarMethodDescriptor, io.grpc.stub.ServerCalls.asyncUnaryCall(higherkindness.mu.rpc.internal.server.unaryCalls.unaryMethod(algebra.bar, None))),
    ...
  )

One tuple is added for each endpoint Foo, Bar, ... in the service.

The problem is that FooMethodDescriptor is a call to a (macro-generated) method which looks like:

def FooMethodDescriptor(implicit
  ReqM: io.grpc.MethodDescriptor.Marshaller[FooRequest],
  RespM: io.grpc.MethodDescriptor.Marshaller[FooResponse]
): ...

When the macro-generated code is compiled, the implicits are expanded. Because these marshallers are auto-derived using avro4s or PBDirect, the resulting reified code can be huge, and that huge chunk of bytecode ends up inside the body of bindService. This can lead to Method too large errors when emitting the bytecode.

Fixed by refactoring the generated code. It now looks like:

object MyService {

  object FooMethodDescriptor {
    def methodDescriptor(implicit reqM: ..., respM: ...): ...
    val _methodDescriptor = methodDescriptor
  }

  object BarMethodDescriptor {
    def methodDescriptor(implicit reqM: ..., respM: ...): ...
    val _methodDescriptor = methodDescriptor
  }

  ...

  def bindService(...) =
    higherkindness.mu.rpc.internal.service.GRPCServiceDefBuilder.build[F](
      "fully.qualified.ServiceName",
      (FooMethodDescriptor._methodDescriptor, ...),
      (BarMethodDescriptor._methodDescriptor, ...),
      ...
    )

}

This way the huge chunks of reified code end up inside the static initializers of the MyService$FooMethodDescriptor$ and MyService$BarMethodDescriptor$ classes instead of inside the body of bindService.

It's still possible to run into the Method too large error if the models are really big and deeply nested, but this gives us a bit of breathing space because the code has been split up by endpoint.

cb372 added 2 commits March 11, 2020 12:38
Before the fix, the macro would generate the following code for the
`bindService` method:

```
def bindService(...) =
  higherkindness.mu.rpc.internal.service.GRPCServiceDefBuilder.build[F](
    "fully.qualified.ServiceName",
    (FooMethodDescriptor, io.grpc.stub.ServerCalls.asyncUnaryCall(higherkindness.mu.rpc.internal.server.unaryCalls.unaryMethod(algebra.foo, None))),
    (BarMethodDescriptor, io.grpc.stub.ServerCalls.asyncUnaryCall(higherkindness.mu.rpc.internal.server.unaryCalls.unaryMethod(algebra.bar, None))),
    ...
  )
```

One tuple is added for each endpoint `Foo`, `Bar`, ... in the service.

The problem is that `FooMethodDescriptor` is a call to a
(macro-generated) method which looks like:

```
def FooMethodDescriptor(implicit
  ReqM: io.grpc.MethodDescriptor.Marshaller[FooRequest],
  RespM: io.grpc.MethodDescriptor.Marshaller[FooResponse]
): ...
```

When the macro-generated code is compiled, the implicits are expanded.
Because these marshallers are auto-derived using avro4s or PBDirect, the
resulting reified code can be huge, and that huge chunk of bytecode ends
up inside the body of `bindService`. This can lead to `Method too large`
errors when emitting the bytecode.

Fixed by refactoring the generated code. It now looks like:

```
object MyService {

  object FooMethodDescriptor {
    def methodDescriptor(implicit reqM: ..., respM: ...): ...
    val _methodDescriptor = methodDescriptor
  }

  object BarMethodDescriptor {
    def methodDescriptor(implicit reqM: ..., respM: ...): ...
    val _methodDescriptor = methodDescriptor
  }

  ...

  def bindService(...) =
    higherkindness.mu.rpc.internal.service.GRPCServiceDefBuilder.build[F](
      "fully.qualified.ServiceName",
      (FooMethodDescriptor._methodDescriptor, ...),
      (BarMethodDescriptor._methodDescriptor, ...),
      ...
    )

}
```

This way the huge chunks of reified code end up inside the static
initializers of the `MyService$FooMethodDescriptor$` and
`MyService$BarMethodDescriptor$ classes instead of inside the body of
`bindService`.

It's still possible to run into the `Method too large` error if the
models are really big and deeply nested, but this gives us a bit of
breathing space because the code has been split up by endpoint.
Copy link
Member

@BenFradet BenFradet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@cb372 cb372 merged commit 7756539 into master Mar 11, 2020
@cb372 cb372 deleted the fix-method-too-large branch March 11, 2020 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants