google-maps-services-java: GeoApiContext should clean up working threads on exit.

Hello guys, I had a problem while doing a PlacesApi.placeAutocomplete request I really don’t know if that is a issue caused by the API or on our implementation.

We are developing an application that uses:

  • Spring Boot 1.3.6.RELEASE
  • Undertow
  • com.google.maps:google-maps-services:0.1.17
# application.properties
server.undertow.worker-threads=1000
server.undertow.io-threads=100
# EC2 AWS
m4.xlarge
# Dockerfile

FROM java:8
RUN apt-get update && apt-get install -y

WORKDIR /app
VOLUME /root/.gradle

COPY gradle/ /app/gradle/
COPY gradlew /app/gradlew
COPY build.gradle /app/build.gradle
COPY src/ /app/src/

COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

CMD ["java","-jar","/app/build/libs/application-1.0.0.jar"]
# docker-entrypoint.sh

#!/bin/bash
set -e
./gradlew build -x test
exec "$@"
@Service
public class GoogleMapsAPIService {
    public List placeAutoComplete(String input, Double latitude, Double longitude, String language) {
        GeoApiContext context = new GeoApiContext().setApiKey(GOOGLEMAPS_KEY);
        LatLng location = new LatLng(latitude, longitude); // -23.52488881961586,-46.67578987777233

        AutocompletePrediction[] autocompletePredictions = PlacesApi.placeAutocomplete(context, input)
            .location(location)
            .language("pt-BR")
            .radius(1000)
            .awaitIgnoreError();

        return assemblyPrediction(autocompletePredictions);
    }

    private List assemblyPrediction(AutocompletePrediction[] autoCompletePredictions) {
        List<Prediction> predictions = new ArrayList<>();

        for (AutocompletePrediction autocompletePrediction : autoCompletePredictions) {
            String placeId = autocompletePrediction.placeId;
            String title = autocompletePrediction.terms[0].value;
            String subTitle = Arrays.stream(autocompletePrediction.terms).skip(1).map(term -> term.value).collect(Collectors.joining(", "));

            predictions.add(new Prediction(placeId, title, subTitle));
        }

        return predictions;
    }
}

Sometimes we get this exception:

2017-04-19 19:11:03,290 [XNIO-3 task-914] DEBUG org.springframework.web.servlet.DispatcherServlet - Last-Modified value for [/predictions] is: -1
2017-04-19 19:11:03,290 [XNIO-3 task-914] INFO  com.application.services.GoogleMapsAPIService - GoogleMapsAPIService.placeAutoComplete
2017-04-19 19:11:03,290 [XNIO-3 task-914] DEBUG com.application.services.GoogleMapsAPIService -  > Passenger: [7459]
2017-04-19 19:11:03,291 [XNIO-3 task-914] DEBUG com.application.services.GoogleMapsAPIService -  > Input:     [Alameda santos 2081]
2017-04-19 19:11:03,291 [XNIO-3 task-914] DEBUG com.application.services.GoogleMapsAPIService -  > Latitude:  [-23.52488881961586]
2017-04-19 19:11:03,291 [XNIO-3 task-914] DEBUG com.application.services.GoogleMapsAPIService -  > Longitude: [-46.67578987777233]
2017-04-19 19:11:03,291 [XNIO-3 task-914] DEBUG com.application.services.GoogleMapsAPIService -  > Language:  [pt-BR]
2017-04-19 19:11:03,291 [XNIO-3 task-914] DEBUG com.application.services.GoogleMapsAPIService -  > Radius:    [1000]
2017-04-19 19:11:03,291 [XNIO-3 task-914] DEBUG org.springframework.web.servlet.DispatcherServlet - Could not complete request
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: unable to create new native thread
        at org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1305)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:979)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
        at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
        at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
        at org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:281)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
        at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)

Caused by: java.lang.OutOfMemoryError: unable to create new native thread
        at java.lang.Thread.start0(Native Method)
        at java.lang.Thread.start(Thread.java:714)
        at com.google.maps.internal.RateLimitExecutorService.<init>(RateLimitExecutorService.java:63)
        at com.google.maps.OkHttpRequestHandler.<init>(OkHttpRequestHandler.java:49)
        at com.google.maps.GeoApiContext.<init>(GeoApiContext.java:88)
        at com.application.services.GoogleMapsAPIService.placeAutoComplete(GoogleMapsAPIService.java:175)
        at com.application.controllers.PredictionsController.predictions(PredictionsController.java:31)

Stacktrace: stacktrace.txt

Do you know what could causes that?

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Comments: 29 (16 by maintainers)

Commits related to this issue

Most upvoted comments

Apparently if you are going to do this in your code like the docs specify:

       GeoApiContext context = new GeoApiContext.Builder().disableRetries()
                .apiKey(googleApiKey)
                .build();
        GeocodingResult[] results = GeocodingApi.geocode(context, address).await();

You must also do this: context.shutdown();

Which the docs do not specify.

This caused us to take major outages today as it would creep up under heavy workloads that did not surface during our regular testing.

Here are the misleading docs

image

Most of us like TLDR; not sure where the context.shutdown(); requirement is hidden in the docs.

Major pain in my ass. Thank you so much Google maps team.

@thpoiani’s solution is what saved us in the end. Thanks for the recommendation.

For example, if you have a Spring Boot app, you should do this in one of you configuration classes:

@Bean
public GeoApiContext geoApiContext (@Value("${myGooleApiKey}") String googleApiKey) {

    GeoApiContext context = new GeoApiContext.Builder().disableRetries()
            .apiKey(googleApiKey)
            .build();
    return context;
}

And in the class where you want to issue a GeocodeAPI call, do this to get a reference to the singleton:

@Autowired
GeoApiContext geoApiContext;

What the code should instead provide is an out of the box singleton implementation which prevents this issue from happening (ties up a single thread eternally, instead of eventually causing the server to run out of threads).

What should also happen is that the docs should clearly state that the wheels of your server are going to come off if you do not also do a context.shutdown() if the above supplied code is going to be continuously called.

I solve that implementing a Singleton to GeoApiContext instance

On May 31, 2017 12:20, “Markus Kühle” notifications@github.com wrote:

This memory leak is still present in version .20. 😕

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/googlemaps/google-maps-services-java/issues/261#issuecomment-305221295, or mute the thread https://github.com/notifications/unsubscribe-auth/ABxiwRF-6Sjh1wJWtoWwHaGEaOq6ZiNuks5r_YVUgaJpZM4NCdZd .

The simplistic answer is that your server is running out of RAM. You probably need to run your server with some diagnostic logging to figure out if you are leaking resources somewhere. A temporary stopgap would be to increase the amount of memory available to the process.

I noticed a problem when instantiating GeoApiContext.Builder.

I want to use my own RequestHandler.Builder. So I used the GeoApiContext.Builder.requestHandlerBuilder(RequestHandler.Builder) method:

try (GeoApiContext context = new GeoApiContext.Builder()
                   .apiKey(apiKey)
                    .requestHandlerBuilder(new MyRequestHandler.Builder())
                    .build()) {
    ...
}

This creates threads that are never closed. If we look at the details, we see that new GeoApiContext.Builder() creates a new OkHttpRequestHandler.Builder:

public Builder() {
    requestHandlerBuilder(new OkHttpRequestHandler.Builder());
}

new OkHttpRequestHandler.Builder() creates a new RateLimitExecutorService:

public Builder() {
      builder = new OkHttpClient.Builder();
      rateLimitExecutorService = new RateLimitExecutorService();
      ...
}

new RateLimitExecutorService() creates a new Thread:

public RateLimitExecutorService() {
    setQueriesPerSecond(DEFAULT_QUERIES_PER_SECOND);
    delayThread = new Thread(this);
    delayThread.setDaemon(true);
    delayThread.setName("RateLimitExecutorDelayThread");
    delayThread.start();
}

When we then call the GeoApiContext.Builder.requestHandlerBuilder(RequestHandler.Builder) method, it replaces the already present RequestHandler.Builder (instance of OkHttpRequestHandler.Builder) which has already created the Thread.

The GeoApiContext.Builder.build() method uses the last RequestHandler.Builder to create the RequestHandler.

When the GeoApiContext.shutdown() method is called, it calls the shutdown() method of the last RequestHandler:

public void shutdown() {
    requestHandler.shutdown();
}

The Threads created by new OkHttpRequestHandler.Builder() are therefore never closed.

After a while, I get the following exception: java.lang.OutOfMemoryError: Unable to create new native thread

A workaround is to not use the GeoApiContext.Builder.requestHandlerBuilder(RequestHandler.Builder) method and to use the GeoApiContext.Builder(RequestHandler.Builder) constructor:

try (final GeoApiContext context = new GeoApiContext.Builder(new MyRequestHandler.Builder())
                    .apiKey(apiKey)
                    .build()) {
    ...
}

I see 2 possible solutions to correct the problem:

  • Delete the GeoApiContext.Builder.requestHandlerBuilder(RequestHandler.Builder) method, and use only the constructor with parameter
  • Move the constructor code from OkHttpRequestHandler.Builder to the build() method

This is still an issue as of version 0.2.4 using the Ok HTTP request handler. Use a singleton google maps API configuration singleton as a workaround.

This will be fixed in the next release version

Re-opening to give myself a reminder that I need to fix this errant behaviour.