diff options
Diffstat (limited to 'java/src')
3 files changed, 130 insertions, 100 deletions
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 99859decf..dc4b0a0cc 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** @@ -69,14 +70,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE; /** - * A static map of time recorders, each of which records the time of accesses to a single binary - * dictionary file. The key for this map is the filename and the value is the shared dictionary - * time recorder associated with that filename. + * A static map of update controllers, each of which records the time of accesses to a single + * binary dictionary file and tracks whether the file is regenerating. The key for this map is + * the filename and the value is the shared dictionary time recorder associated with that + * filename. */ - private static volatile ConcurrentHashMap<String, DictionaryTimeRecorder> - sFilenameDictionaryTimeRecorderMap = CollectionUtils.newConcurrentHashMap(); + private static final ConcurrentHashMap<String, DictionaryUpdateController> + sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap(); - private static volatile ConcurrentHashMap<String, PrioritizedSerialExecutor> + private static final ConcurrentHashMap<String, PrioritizedSerialExecutor> sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap(); /** The application context. */ @@ -103,13 +105,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private final boolean mIsUpdatable; // TODO: remove, once dynamic operations is serialized - /** Records access to the shared binary dictionary file across multiple instances. */ - private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder; + /** Controls updating the shared binary dictionary file across multiple instances. */ + private final DictionaryUpdateController mFilenameDictionaryUpdateController; // TODO: remove, once dynamic operations is serialized - /** Records access to the local binary dictionary for this instance. */ - private final DictionaryTimeRecorder mPerInstanceDictionaryTimeRecorder = - new DictionaryTimeRecorder(); + /** Controls updating the local binary dictionary for this instance. */ + private final DictionaryUpdateController mPerInstanceDictionaryUpdateController = + new DictionaryUpdateController(); /* A extension for a binary dictionary file. */ public static final String DICT_FILE_EXTENSION = ".dict"; @@ -131,15 +133,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected abstract boolean hasContentChanged(); /** - * Gets the dictionary time recorder for the given filename. + * Gets the dictionary update controller for the given filename. */ - private static DictionaryTimeRecorder getDictionaryTimeRecorder( + private static DictionaryUpdateController getDictionaryUpdateController( String filename) { - DictionaryTimeRecorder recorder = sFilenameDictionaryTimeRecorderMap.get(filename); + DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename); if (recorder == null) { - synchronized(sFilenameDictionaryTimeRecorderMap) { - recorder = new DictionaryTimeRecorder(); - sFilenameDictionaryTimeRecorderMap.put(filename, recorder); + synchronized(sFilenameDictionaryUpdateControllerMap) { + recorder = new DictionaryUpdateController(); + sFilenameDictionaryUpdateControllerMap.put(filename, recorder); } } return recorder; @@ -189,7 +191,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mContext = context; mIsUpdatable = isUpdatable; mBinaryDictionary = null; - mFilenameDictionaryTimeRecorder = getDictionaryTimeRecorder(filename); + mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename); // Currently, only dynamic personalization dictionary is updatable. mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable); } @@ -347,6 +349,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final int sessionId) { reloadDictionaryIfRequired(); + if (isRegenerating()) { + return null; + } final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder = new AsyncResultHolder<ArrayList<SuggestedWordInfo>>(); @@ -407,6 +412,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } protected boolean isValidWordInner(final String word) { + if (isRegenerating()) { + return false; + } final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); getExecutor(mFilename).executePrioritized(new Runnable() { @Override @@ -432,7 +440,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * dictionary exists, this method will generate one. */ protected void loadDictionary() { - mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis(); + mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = SystemClock.uptimeMillis(); reloadDictionaryIfRequired(); } @@ -443,8 +451,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private void loadBinaryDictionary() { if (DEBUG) { Log.d(TAG, "Loading binary dictionary: " + mFilename + " request=" - + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update=" - + mFilenameDictionaryTimeRecorder.mLastUpdateTime); + + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update=" + + mFilenameDictionaryUpdateController.mLastUpdateTime); } final File file = new File(mContext.getFilesDir(), mFilename); @@ -482,8 +490,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private void writeBinaryDictionary() { if (DEBUG) { Log.d(TAG, "Generating binary dictionary: " + mFilename + " request=" - + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update=" - + mFilenameDictionaryTimeRecorder.mLastUpdateTime); + + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update=" + + mFilenameDictionaryUpdateController.mLastUpdateTime); } if (needsToReloadBeforeWriting()) { mDictionaryWriter.clear(); @@ -517,11 +525,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ protected void setRequiresReload(final boolean requiresRebuild) { final long time = SystemClock.uptimeMillis(); - mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = time; - mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = time; + mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time; + mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time; if (DEBUG) { Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update=" - + mFilenameDictionaryTimeRecorder.mLastUpdateTime); + + mFilenameDictionaryUpdateController.mLastUpdateTime); } } @@ -530,14 +538,26 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ public final void reloadDictionaryIfRequired() { if (!isReloadRequired()) return; - reloadDictionary(); + if (setIsRegeneratingIfNotRegenerating()) { + reloadDictionary(); + } } /** * Returns whether a dictionary reload is required. */ private boolean isReloadRequired() { - return mBinaryDictionary == null || mPerInstanceDictionaryTimeRecorder.isOutOfDate(); + return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate(); + } + + private boolean isRegenerating() { + return mFilenameDictionaryUpdateController.mIsRegenerating.get(); + } + + // Returns whether the dictionary can be regenerated. + private boolean setIsRegeneratingIfNotRegenerating() { + return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet( + false /* expect */ , true /* update */); } /** @@ -550,39 +570,44 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { getExecutor(mFilename).execute(new Runnable() { @Override public void run() { - final long time = SystemClock.uptimeMillis(); - final boolean dictionaryFileExists = dictionaryFileExists(); - if (mFilenameDictionaryTimeRecorder.isOutOfDate() || !dictionaryFileExists) { - // If the shared dictionary file does not exist or is out of date, the first - // instance that acquires the lock will generate a new one. - if (hasContentChanged() || !dictionaryFileExists) { - // If the source content has changed or the dictionary does not exist, - // rebuild the binary dictionary. Empty dictionaries are supported (in the - // case where loadDictionaryAsync() adds nothing) in order to provide a - // uniform framework. - mFilenameDictionaryTimeRecorder.mLastUpdateTime = time; + try { + final long time = SystemClock.uptimeMillis(); + final boolean dictionaryFileExists = dictionaryFileExists(); + if (mFilenameDictionaryUpdateController.isOutOfDate() + || !dictionaryFileExists) { + // If the shared dictionary file does not exist or is out of date, the + // first instance that acquires the lock will generate a new one. + if (hasContentChanged() || !dictionaryFileExists) { + // If the source content has changed or the dictionary does not exist, + // rebuild the binary dictionary. Empty dictionaries are supported (in + // the case where loadDictionaryAsync() adds nothing) in order to + // provide a uniform framework. + mFilenameDictionaryUpdateController.mLastUpdateTime = time; + writeBinaryDictionary(); + loadBinaryDictionary(); + } else { + // If not, the reload request was unnecessary so revert + // LastUpdateRequestTime to LastUpdateTime. + mFilenameDictionaryUpdateController.mLastUpdateRequestTime = + mFilenameDictionaryUpdateController.mLastUpdateTime; + } + } else if (mBinaryDictionary == null || + mPerInstanceDictionaryUpdateController.mLastUpdateTime + < mFilenameDictionaryUpdateController.mLastUpdateTime) { + // Otherwise, if the local dictionary is older than the shared dictionary, + // load the shared dictionary. + loadBinaryDictionary(); + } + if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) { + // Binary dictionary is not valid. Regenerate the dictionary file. + mFilenameDictionaryUpdateController.mLastUpdateTime = time; writeBinaryDictionary(); loadBinaryDictionary(); - } else { - // If not, the reload request was unnecessary so revert - // LastUpdateRequestTime to LastUpdateTime. - mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = - mFilenameDictionaryTimeRecorder.mLastUpdateTime; } - } else if (mBinaryDictionary == null || - mPerInstanceDictionaryTimeRecorder.mLastUpdateTime - < mFilenameDictionaryTimeRecorder.mLastUpdateTime) { - // Otherwise, if the local dictionary is older than the shared dictionary, load - // the shared dictionary. - loadBinaryDictionary(); - } - if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) { - // Binary dictionary is not valid. Regenerate the dictionary file. - mFilenameDictionaryTimeRecorder.mLastUpdateTime = time; - writeBinaryDictionary(); - loadBinaryDictionary(); + mPerInstanceDictionaryUpdateController.mLastUpdateTime = time; + } finally { + mFilenameDictionaryUpdateController.mIsRegenerating.set(false); } - mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time; } }); } @@ -622,14 +647,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } /** - * Time recorder for tracking whether the dictionary is out of date. + * For tracking whether the dictionary is out of date and the dictionary is regenerating. * Can be shared across multiple dictionary instances that access the same filename. */ - private static class DictionaryTimeRecorder { - private volatile long mLastUpdateTime = 0; - private volatile long mLastUpdateRequestTime = 0; + private static class DictionaryUpdateController { + public volatile long mLastUpdateTime = 0; + public volatile long mLastUpdateRequestTime = 0; + public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean(); - private boolean isOutOfDate() { + public boolean isOutOfDate() { return (mLastUpdateRequestTime > mLastUpdateTime); } } @@ -687,7 +713,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { holder.set(mBinaryDictionary.isValidWord(word)); } else { holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter) - .isInDictionaryForTests(word)); + .isInBigramListForTests(word)); } } } diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java index 0af028a9e..305088536 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java +++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java @@ -79,7 +79,7 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr public void addUnigramWord(final String word, final String shortcutTarget, final int frequency, final boolean isNotAWord) { if (mBigramList.size() > mMaxHistoryBigrams * 2) { - // Too many entries: just stop adding new vocabrary and wait next refresh. + // Too many entries: just stop adding new vocabulary and wait next refresh. return; } mExpandableDictionary.addWord(word, shortcutTarget, frequency); @@ -90,7 +90,7 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr public void addBigramWords(final String word0, final String word1, final int frequency, final boolean isValid, final long lastModifiedTime) { if (mBigramList.size() > mMaxHistoryBigrams * 2) { - // Too many entries: just stop adding new vocabrary and wait next refresh. + // Too many entries: just stop adding new vocabulary and wait next refresh. return; } if (lastModifiedTime > 0) { @@ -176,8 +176,8 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr } @UsedForTesting - public boolean isInDictionaryForTests(final String word) { + public boolean isInBigramListForTests(final String word) { // TODO: Use native method to determine whether the word is in dictionary or not - return mBigramList.containsKey(word); + return mBigramList.containsKey(word) || mBigramList.getBigrams(null).containsKey(word); } } diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java index 5dc0b5893..201a70d42 100644 --- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java +++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java @@ -16,8 +16,11 @@ package com.android.inputmethod.latin.utils; -import java.util.ArrayDeque; import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * An object that executes submitted tasks using a thread. @@ -27,19 +30,20 @@ public class PrioritizedSerialExecutor { private final Object mLock = new Object(); - // The default value of capacities of task queues. - private static final int TASK_QUEUE_CAPACITY = 1000; private final Queue<Runnable> mTasks; private final Queue<Runnable> mPrioritizedTasks; private boolean mIsShutdown; + private final ThreadPoolExecutor mThreadPoolExecutor; // The task which is running now. private Runnable mActive; public PrioritizedSerialExecutor() { - mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY); - mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY); + mTasks = new ConcurrentLinkedQueue<Runnable>(); + mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>(); mIsShutdown = false; + mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, + 0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1)); } /** @@ -59,7 +63,16 @@ public class PrioritizedSerialExecutor { public void execute(final Runnable r) { synchronized(mLock) { if (!mIsShutdown) { - mTasks.offer(r); + mTasks.offer(new Runnable() { + @Override + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); if (mActive == null) { scheduleNext(); } @@ -74,45 +87,36 @@ public class PrioritizedSerialExecutor { public void executePrioritized(final Runnable r) { synchronized(mLock) { if (!mIsShutdown) { - mPrioritizedTasks.offer(r); - if (mActive == null) { + mPrioritizedTasks.offer(new Runnable() { + @Override + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { scheduleNext(); } } } } - private boolean fetchNextTasks() { - synchronized(mLock) { - mActive = mPrioritizedTasks.poll(); - if (mActive == null) { - mActive = mTasks.poll(); - } - return mActive != null; + private boolean fetchNextTasksLocked() { + mActive = mPrioritizedTasks.poll(); + if (mActive == null) { + mActive = mTasks.poll(); } + return mActive != null; } private void scheduleNext() { synchronized(mLock) { - if (!fetchNextTasks()) { - return; + if (fetchNextTasksLocked()) { + mThreadPoolExecutor.execute(mActive); } - new Thread(new Runnable() { - @Override - public void run() { - try { - do { - synchronized(mLock) { - if (mActive != null) { - mActive.run(); - } - } - } while (fetchNextTasks()); - } finally { - scheduleNext(); - } - } - }).start(); } } |