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
- fixed thread leak #261 — committed to stafichuk/google-maps-services-java by stafichuk 7 years ago
- fixed thread leak #261 — committed to stafichuk/google-maps-services-java by stafichuk 7 years ago
- added a shutdown method to GeoApiContext which stops RateLimitExecutorDelayThread #261 — committed to stafichuk/google-maps-services-java by stafichuk 7 years ago
- Merge pull request #365 from stafichuk/master added a shutdown method to GeoApiContext which stops RateLimitExecutorDelayThread #261 — committed to googlemaps/google-maps-services-java by domesticmouse 7 years ago
Apparently if you are going to do this in your code like the docs specify:
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
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:
And in the class where you want to issue a GeocodeAPI call, do this to get a reference to the singleton:
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:
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:This creates threads that are never closed. If we look at the details, we see that
new GeoApiContext.Builder()
creates a newOkHttpRequestHandler.Builder
:new OkHttpRequestHandler.Builder()
creates a newRateLimitExecutorService
:new RateLimitExecutorService()
creates a new Thread:When we then call the
GeoApiContext.Builder.requestHandlerBuilder(RequestHandler.Builder)
method, it replaces the already presentRequestHandler.Builder
(instance ofOkHttpRequestHandler.Builder
) which has already created the Thread.The
GeoApiContext.Builder.build()
method uses the lastRequestHandler.Builder
to create theRequestHandler
.When the
GeoApiContext.shutdown()
method is called, it calls theshutdown()
method of the lastRequestHandler
: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 theGeoApiContext.Builder(RequestHandler.Builder)
constructor:I see 2 possible solutions to correct the problem:
GeoApiContext.Builder.requestHandlerBuilder(RequestHandler.Builder)
method, and use only the constructor with parameterOkHttpRequestHandler.Builder
to thebuild()
methodThis 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.