Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2bc980ee authored by Jean Chalard's avatar Jean Chalard Committed by Android (Google) Code Review
Browse files

Merge "Serialize commands sent to a separate threads"

parents 544e891c 6a7a5698
Loading
Loading
Loading
Loading
+34 −25
Original line number Diff line number Diff line
@@ -22,13 +22,14 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import com.android.inputmethod.latin.R;

import java.util.Locale;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
@@ -92,21 +93,27 @@ public final class DictionaryService extends Service {
    private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);

    /**
     * The last seen start Id. This must be stored because we must only call stopSelfResult() with
     * the last seen Id, or the service won't stop.
     * An executor that serializes tasks given to it.
     */
    private int mLastSeenStartId;

    /**
     * The command count. We need this because we need to not call stopSelfResult() while we still
     * have commands running.
     */
    private int mCommandCount;
    private ThreadPoolExecutor mExecutor;
    private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;

    @Override
    public void onCreate() {
        mLastSeenStartId = 0;
        mCommandCount = 0;
        // By default, a thread pool executor does not timeout its core threads, so it will
        // never kill them when there isn't any work to do any more. That would mean the service
        // can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow
        // the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing
        // the process to be reclaimed by the system any time after that if it's not doing
        // anything else.
        // Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the
        // superclass ExecutorService which does not have the #allowCoreThreadTimeOut method,
        // so we can't use that.
        mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
                WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
                TimeUnit.SECONDS /* unit for keepAliveTime */,
                new LinkedBlockingQueue<Runnable>() /* workQueue */);
        mExecutor.allowCoreThreadTimeOut(true);
    }

    @Override
@@ -131,33 +138,35 @@ public final class DictionaryService extends Service {
     * - Handle a finished download.
     *     This executes the actions that must be taken after a file (metadata or dictionary data
     *     has been downloaded (or failed to download).
     * The commands that can be spun an another thread will be executed serially, in order, on
     * a worker thread that is created on demand and terminates after a short while if there isn't
     * any work left to do.
     */
    @Override
    public synchronized int onStartCommand(final Intent intent, final int flags,
            final int startId) {
        final DictionaryService self = this;
        mLastSeenStartId = startId;
        mCommandCount += 1;
        if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
            // This is a UI action, it can't be run in another thread
            showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
                    intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
        } else {
            // If it's a command that does not require UI, create a thread to do the work
            // and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands.
            new Thread("updateOrFinishDownload") {
            // If it's a command that does not require UI, arrange for the work to be done on a
            // separate thread, so that we can return right away. The executor will spawn a thread
            // if necessary, or reuse a thread that has become idle as appropriate.
            // DATE_CHANGED or UPDATE_NOW are examples of commands that can be done on another
            // thread.
            mExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    dispatchBroadcast(self, intent);
                    synchronized(self) {
                        if (--mCommandCount <= 0) {
                            if (!stopSelfResult(mLastSeenStartId)) {
                                Log.e(TAG, "Can't stop ourselves");
                            }
                        }
                    }
                    // Since calls to onStartCommand are serialized, the submissions to the executor
                    // are serialized. That means we are guaranteed to call the stopSelfResult()
                    // in the same order that we got them, so we don't need to take care of the
                    // order.
                    stopSelfResult(startId);
                }
            }.start();
            });
        }
        return Service.START_REDELIVER_INTENT;
    }