jkb016
jkb016

Reputation: 524

Objectify - loading child entity in separate thread produces inconsistent results

fetch a list of objects . Use multi threading to fetch children objects and do some processing. collect all results and send to UI. threads produce inconsistent results. Some of the threads throw an error while some work fine. sample code : entities : Parent, Child

Parent {
    Ref<Child> childKey ;
    public Child getChild(){
        return childKey.get();
    }
}

    List<Parent> parents ; //get from db
    ExecutorService executorService = Executors.newFixedThreadPool(20, ThreadManager.currentRequestThreadFactory());
            List<Future<ParentTO>> futures = new ArrayList<>();
            for (Parent parent : parents) {
                Future<ParentTO> future = executorService.submit(() -> {
                return  ObjectifyService.run(new Work<ParentTO>() {
                        public ParentTO run() {
                            parent.getChild();                      ParentTO to = new ParentTO();

                            return to;
                        }
                    });
                });

            }

Error only shows on appengine and for some threads. It works fine on local server. If i reload page i see different results every time. Without threads it works fine.

java.util.concurrent.ExecutionException: com.googlecode.objectify.LoadException: Error loading Parent("FND1clkiTUa_lWsJow-Dxwdmadma")/Child(1): null
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:206)
    at com.netkiller.erp.service.ParentService.ParentsToApprovalTOs(ParentService.java:1720)
    at com.netkiller.erp.service.ParentService.getSharedParentsUI(ParentService.java:2000)
    at com.netkiller.common.controller.ParentController.fetchSharedParents(ParentController.java:1266)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:214)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:690)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:848)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1772)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.netkiller.common.filter.OpenIdFilter.doFilter(OpenIdFilter.java:859)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.netkiller.common.filter.ServiceFilter.doFilter(ServiceFilter.java:100)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.netkiller.common.filter.SecureProtocolFilter.doFilter(SecureProtocolFilter.java:35)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.googlecode.objectify.ObjectifyFilter.doFilter(ObjectifyFilter.java:48)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at com.google.apphosting.utils.servlet.JdbcMySqlConnectionCleanupFilter.doFilter(JdbcMySqlConnectionCleanupFilter.java:60)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1759)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:582)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:524)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
    at com.google.apphosting.runtime.jetty9.ParseBlobUploadHandler.handle(ParseBlobUploadHandler.java:119)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1182)
    at com.google.apphosting.runtime.jetty9.AppEngineWebAppContext.doHandle(AppEngineWebAppContext.java:183)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:512)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at com.google.apphosting.runtime.jetty9.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:293)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
    at org.eclipse.jetty.server.Server.handle(Server.java:539)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:333)
    at com.google.apphosting.runtime.jetty9.RpcConnection.handle(RpcConnection.java:213)
    at com.google.apphosting.runtime.jetty9.RpcConnector.serviceRequest(RpcConnector.java:81)
    at com.google.apphosting.runtime.jetty9.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:123)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchServletRequest(JavaRuntime.java:692)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.dispatchRequest(JavaRuntime.java:655)
    at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.run(JavaRuntime.java:625)
    at com.google.apphosting.runtime.JavaRuntime$NullSandboxRequestRunnable.run(JavaRuntime.java:817)
    at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:269)
    at java.lang.Thread.run(Thread.java:748)
Caused by: com.googlecode.objectify.LoadException: Error loading Parent("FND1clkiTUa_lWsJow-Dxwdmadma")/Child(1): null
    at com.googlecode.objectify.impl.EntityMetadata.load(EntityMetadata.java:78)
    at com.googlecode.objectify.impl.LoadEngine.load(LoadEngine.java:185)
    at com.googlecode.objectify.impl.LoadEngine$1.nowUncached(LoadEngine.java:141)
    at com.googlecode.objectify.impl.LoadEngine$1.nowUncached(LoadEngine.java:127)
    at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
    at com.googlecode.objectify.impl.Round$1.nowUncached(Round.java:71)
    at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
    at com.googlecode.objectify.impl.LoaderImpl.now(LoaderImpl.java:251)
    at com.googlecode.objectify.impl.ref.LiveRef.get(LiveRef.java:47)
    at com.netkiller.erp.domain.Parent.getChild(Parent.java:187)
    at com.netkiller.common.dto.ApprovalTO.<init>(ApprovalTO.java:503)
    at com.netkiller.erp.service.ParentService$2.run(ParentService.java:1708)
    at com.netkiller.erp.service.ParentService$2.run(ParentService.java:1)
    at com.googlecode.objectify.ObjectifyService.run(ObjectifyService.java:81)
    at com.netkiller.erp.service.ParentService.lambda$8(ParentService.java:1706)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at com.google.apphosting.runtime.ApiProxyImpl$CurrentRequestThreadFactory.lambda$newThread$0(ApiProxyImpl.java:1213)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.google.apphosting.runtime.ApiProxyImpl$CurrentRequestThreadFactory.lambda$newThread$1(ApiProxyImpl.java:1209)
    at java.lang.Thread.run(Thread.java:748)
    at com.google.apphosting.runtime.ApiProxyImpl$CurrentRequestThread.run(ApiProxyImpl.java:1185)
Caused by: java.util.NoSuchElementException
    at java.util.ArrayDeque.removeLast(ArrayDeque.java:295)
    at com.googlecode.objectify.impl.translate.LoadContext.exitContainerContext(LoadContext.java:141)
    at com.googlecode.objectify.impl.translate.ClassPopulator.load(ClassPopulator.java:119)
    at com.googlecode.objectify.impl.translate.ClassTranslator.loadSafe(ClassTranslator.java:122)
    at com.googlecode.objectify.impl.translate.ClassTranslator.loadSafe(ClassTranslator.java:21)
    at com.googlecode.objectify.impl.translate.NullSafeTranslator.load(NullSafeTranslator.java:17)
    at com.googlecode.objectify.impl.EntityMetadata.load(EntityMetadata.java:74)
    ... 22 more

Thanks for any insight on the issue.

Upvotes: 0

Views: 138

Answers (1)

stickfigure
stickfigure

Reputation: 13556

It's hard to tell exactly what's going on from the provided code, but it's easy to accidentally contaminate data when operating across multiple threads.

To reduce the chance that you're accidentally pulling data from the wrong thread's session, use ofy().load() to load everything instead of following refs. You can pass a Ref to ofy().load().ref(theRef).

If you always call ofy() then you will always get an instance correct for your thread of execution. Try that out and if you still have an issue, we can continue this discussion with whatever error message you see.

Upvotes: 1

Related Questions