Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objectify and TimerTask: No API environment is registered for this thread

I'm trying to get a TimerTask set up to remove entries from Google App Engine's dataStore periodically. So I set up a ServletContextListener with a Timer.

Inside the contextInitialized, I have registered my Objectify classes:

ObjectifyService.register(Person.class);

However, when the task actually runs, it complains that no API environment has been set up:

Exception in thread "Timer-0" java.lang.NullPointerException: No API environment is registered for this thread.
    at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppId(DatastoreApiHelper.java:80)
    at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppIdNamespace(DatastoreApiHelper.java:90)
    at com.google.appengine.api.datastore.Query.<init>(Query.java:214)
    at com.google.appengine.api.datastore.Query.<init>(Query.java:143)
    at com.googlecode.objectify.impl.cmd.QueryImpl.<init>(QueryImpl.java:72)
    at com.googlecode.objectify.impl.cmd.LoadTypeImpl.createQuery(LoadTypeImpl.java:50)
    at com.googlecode.objectify.impl.cmd.LoadTypeImpl.filter(LoadTypeImpl.java:58)
    at myApp.MyServletContextListener$MyTask.run(MyServletContextListener.java:58)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

Any ideas? I've tried changing the line that registers the class to ObjectifyService.factory().register(Person.class); but it didn't seem to help.

like image 587
Charlotte Tan Avatar asked Apr 12 '13 16:04

Charlotte Tan


1 Answers

From the documentation of java.util.Timer class:

Corresponding to each Timer object is a single background thread.

And peeking to the inner code of the java.util.Timer class, we can see that it basically instantiates the thread by invoking new Thread().

Meanwhile, from App Engine's documentation about the use of threads in their Java sandbox:

You must use one of the methods on ThreadManager to create your threads. You cannot invoke new Thread() yourself or use the default thread factory.

So what happened here is the Timer object instantiated their own thread, which then executes the Objectify queries, but since threads instantiated outside ThreadManager does not have the proper App Engine API environment set up for them, it throws an exception.

You need to refactor your code to avoid using the Timer and TimerTask classes and use basic threads instead. For example, instead of using:

import java.util.Timer;
import java.util.TimerTask;

...

Timer timer = new Timer();
timer.schedule( new TimerTask()
{
    @Override
    public void run()
    {
        // Objectify query here.
    }
}, 5000 );

You could instead use:

import com.google.appengine.api.ThreadManager;

...

final long tScheduleDelay = 5000;
ThreadManager.createThreadForCurrentRequest( new Runnable()
{
    @Override
    public void run()
    {
        try
        {
            Thread.sleep( tScheduleDelay );
        }
        catch ( InterruptedException ex )
        {
            // log possible exception
        }

        // Objectify query here.
    }
} ).start();
like image 194
Ibrahim Arief Avatar answered Sep 28 '22 00:09

Ibrahim Arief