Stop reading dictionary while regenerating.

Bug: 10831272

Change-Id: Iead7268a9371b48d729a5f65074ccbc05f3185db
main
Keisuke Kuroyanagi 2013-09-27 21:44:26 +09:00
parent b7dfacd31e
commit e74d4a184b
1 changed files with 88 additions and 62 deletions

View File

@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
@ -69,14 +70,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE; FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE;
/** /**
* A static map of time recorders, each of which records the time of accesses to a single binary * A static map of update controllers, each of which records the time of accesses to a single
* dictionary file. The key for this map is the filename and the value is the shared dictionary * binary dictionary file and tracks whether the file is regenerating. The key for this map is
* time recorder associated with that filename. * the filename and the value is the shared dictionary time recorder associated with that
* filename.
*/ */
private static volatile ConcurrentHashMap<String, DictionaryTimeRecorder> private static final ConcurrentHashMap<String, DictionaryUpdateController>
sFilenameDictionaryTimeRecorderMap = CollectionUtils.newConcurrentHashMap(); sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
private static volatile ConcurrentHashMap<String, PrioritizedSerialExecutor> private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap(); sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
/** The application context. */ /** The application context. */
@ -103,13 +105,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final boolean mIsUpdatable; private final boolean mIsUpdatable;
// TODO: remove, once dynamic operations is serialized // TODO: remove, once dynamic operations is serialized
/** Records access to the shared binary dictionary file across multiple instances. */ /** Controls updating the shared binary dictionary file across multiple instances. */
private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder; private final DictionaryUpdateController mFilenameDictionaryUpdateController;
// TODO: remove, once dynamic operations is serialized // TODO: remove, once dynamic operations is serialized
/** Records access to the local binary dictionary for this instance. */ /** Controls updating the local binary dictionary for this instance. */
private final DictionaryTimeRecorder mPerInstanceDictionaryTimeRecorder = private final DictionaryUpdateController mPerInstanceDictionaryUpdateController =
new DictionaryTimeRecorder(); new DictionaryUpdateController();
/* A extension for a binary dictionary file. */ /* A extension for a binary dictionary file. */
public static final String DICT_FILE_EXTENSION = ".dict"; public static final String DICT_FILE_EXTENSION = ".dict";
@ -131,15 +133,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected abstract boolean hasContentChanged(); 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) { String filename) {
DictionaryTimeRecorder recorder = sFilenameDictionaryTimeRecorderMap.get(filename); DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename);
if (recorder == null) { if (recorder == null) {
synchronized(sFilenameDictionaryTimeRecorderMap) { synchronized(sFilenameDictionaryUpdateControllerMap) {
recorder = new DictionaryTimeRecorder(); recorder = new DictionaryUpdateController();
sFilenameDictionaryTimeRecorderMap.put(filename, recorder); sFilenameDictionaryUpdateControllerMap.put(filename, recorder);
} }
} }
return recorder; return recorder;
@ -189,7 +191,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mContext = context; mContext = context;
mIsUpdatable = isUpdatable; mIsUpdatable = isUpdatable;
mBinaryDictionary = null; mBinaryDictionary = null;
mFilenameDictionaryTimeRecorder = getDictionaryTimeRecorder(filename); mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename);
// Currently, only dynamic personalization dictionary is updatable. // Currently, only dynamic personalization dictionary is updatable.
mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable); mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
} }
@ -347,6 +349,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId) { final int sessionId) {
reloadDictionaryIfRequired(); reloadDictionaryIfRequired();
if (isRegenerating()) {
return null;
}
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder = final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
new AsyncResultHolder<ArrayList<SuggestedWordInfo>>(); new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
@ -407,6 +412,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
} }
protected boolean isValidWordInner(final String word) { protected boolean isValidWordInner(final String word) {
if (isRegenerating()) {
return false;
}
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
getExecutor(mFilename).executePrioritized(new Runnable() { getExecutor(mFilename).executePrioritized(new Runnable() {
@Override @Override
@ -432,7 +440,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* dictionary exists, this method will generate one. * dictionary exists, this method will generate one.
*/ */
protected void loadDictionary() { protected void loadDictionary() {
mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis(); mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
reloadDictionaryIfRequired(); reloadDictionaryIfRequired();
} }
@ -443,8 +451,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private void loadBinaryDictionary() { private void loadBinaryDictionary() {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Loading binary dictionary: " + mFilename + " request=" Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
+ mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update=" + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ mFilenameDictionaryTimeRecorder.mLastUpdateTime); + mFilenameDictionaryUpdateController.mLastUpdateTime);
} }
final File file = new File(mContext.getFilesDir(), mFilename); final File file = new File(mContext.getFilesDir(), mFilename);
@ -482,8 +490,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private void writeBinaryDictionary() { private void writeBinaryDictionary() {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Generating binary dictionary: " + mFilename + " request=" Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
+ mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update=" + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ mFilenameDictionaryTimeRecorder.mLastUpdateTime); + mFilenameDictionaryUpdateController.mLastUpdateTime);
} }
if (needsToReloadBeforeWriting()) { if (needsToReloadBeforeWriting()) {
mDictionaryWriter.clear(); mDictionaryWriter.clear();
@ -517,11 +525,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/ */
protected void setRequiresReload(final boolean requiresRebuild) { protected void setRequiresReload(final boolean requiresRebuild) {
final long time = SystemClock.uptimeMillis(); final long time = SystemClock.uptimeMillis();
mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = time; mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time;
mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = time; mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update=" 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() { public final void reloadDictionaryIfRequired() {
if (!isReloadRequired()) return; if (!isReloadRequired()) return;
if (setIsRegeneratingIfNotRegenerating()) {
reloadDictionary(); reloadDictionary();
} }
}
/** /**
* Returns whether a dictionary reload is required. * Returns whether a dictionary reload is required.
*/ */
private boolean isReloadRequired() { 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() { getExecutor(mFilename).execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try {
final long time = SystemClock.uptimeMillis(); final long time = SystemClock.uptimeMillis();
final boolean dictionaryFileExists = dictionaryFileExists(); final boolean dictionaryFileExists = dictionaryFileExists();
if (mFilenameDictionaryTimeRecorder.isOutOfDate() || !dictionaryFileExists) { if (mFilenameDictionaryUpdateController.isOutOfDate()
// If the shared dictionary file does not exist or is out of date, the first || !dictionaryFileExists) {
// instance that acquires the lock will generate a new one. // 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 (hasContentChanged() || !dictionaryFileExists) {
// If the source content has changed or the dictionary does not exist, // If the source content has changed or the dictionary does not exist,
// rebuild the binary dictionary. Empty dictionaries are supported (in the // rebuild the binary dictionary. Empty dictionaries are supported (in
// case where loadDictionaryAsync() adds nothing) in order to provide a // the case where loadDictionaryAsync() adds nothing) in order to
// uniform framework. // provide a uniform framework.
mFilenameDictionaryTimeRecorder.mLastUpdateTime = time; mFilenameDictionaryUpdateController.mLastUpdateTime = time;
writeBinaryDictionary(); writeBinaryDictionary();
loadBinaryDictionary(); loadBinaryDictionary();
} else { } else {
// If not, the reload request was unnecessary so revert // If not, the reload request was unnecessary so revert
// LastUpdateRequestTime to LastUpdateTime. // LastUpdateRequestTime to LastUpdateTime.
mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = mFilenameDictionaryUpdateController.mLastUpdateRequestTime =
mFilenameDictionaryTimeRecorder.mLastUpdateTime; mFilenameDictionaryUpdateController.mLastUpdateTime;
} }
} else if (mBinaryDictionary == null || } else if (mBinaryDictionary == null ||
mPerInstanceDictionaryTimeRecorder.mLastUpdateTime mPerInstanceDictionaryUpdateController.mLastUpdateTime
< mFilenameDictionaryTimeRecorder.mLastUpdateTime) { < mFilenameDictionaryUpdateController.mLastUpdateTime) {
// Otherwise, if the local dictionary is older than the shared dictionary, load // Otherwise, if the local dictionary is older than the shared dictionary,
// the shared dictionary. // load the shared dictionary.
loadBinaryDictionary(); loadBinaryDictionary();
} }
if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) { if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
// Binary dictionary is not valid. Regenerate the dictionary file. // Binary dictionary is not valid. Regenerate the dictionary file.
mFilenameDictionaryTimeRecorder.mLastUpdateTime = time; mFilenameDictionaryUpdateController.mLastUpdateTime = time;
writeBinaryDictionary(); writeBinaryDictionary();
loadBinaryDictionary(); loadBinaryDictionary();
} }
mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time; mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
} finally {
mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
}
} }
}); });
} }
@ -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. * Can be shared across multiple dictionary instances that access the same filename.
*/ */
private static class DictionaryTimeRecorder { private static class DictionaryUpdateController {
private volatile long mLastUpdateTime = 0; public volatile long mLastUpdateTime = 0;
private volatile long mLastUpdateRequestTime = 0; public volatile long mLastUpdateRequestTime = 0;
public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
private boolean isOutOfDate() { public boolean isOutOfDate() {
return (mLastUpdateRequestTime > mLastUpdateTime); return (mLastUpdateRequestTime > mLastUpdateTime);
} }
} }