Fix PrioritizedSerialExecutor.
It was possible that fetchNextTasks() would be called by multiple threads concurrently. If it happens, some tasks in the task queues might be ignored. cherrypick of Idc81c43c45e382da3850cc55b9a42c281548d2a8 bug: 10957075 Change-Id: I41bf90dda15306ee879350f96a791d940ea31da1
This commit is contained in:
parent
ef63947f7d
commit
ceb364c041
4 changed files with 46 additions and 47 deletions
|
@ -727,7 +727,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
|
||||||
holder.set(mBinaryDictionary.isValidWord(word));
|
holder.set(mBinaryDictionary.isValidWord(word));
|
||||||
} else {
|
} else {
|
||||||
holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
|
holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
|
||||||
.isInDictionaryForTests(word));
|
.isInBigramListForTests(word));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
|
||||||
public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
|
public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
|
||||||
final boolean isNotAWord) {
|
final boolean isNotAWord) {
|
||||||
if (mBigramList.size() > mMaxHistoryBigrams * 2) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
mExpandableDictionary.addWord(word, shortcutTarget, frequency);
|
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,
|
public void addBigramWords(final String word0, final String word1, final int frequency,
|
||||||
final boolean isValid, final long lastModifiedTime) {
|
final boolean isValid, final long lastModifiedTime) {
|
||||||
if (mBigramList.size() > mMaxHistoryBigrams * 2) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (lastModifiedTime > 0) {
|
if (lastModifiedTime > 0) {
|
||||||
|
@ -176,8 +176,8 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
|
||||||
}
|
}
|
||||||
|
|
||||||
@UsedForTesting
|
@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
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
|
|
||||||
package com.android.inputmethod.latin.utils;
|
package com.android.inputmethod.latin.utils;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Queue;
|
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.
|
* An object that executes submitted tasks using a thread.
|
||||||
|
@ -27,19 +30,20 @@ public class PrioritizedSerialExecutor {
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
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> mTasks;
|
||||||
private final Queue<Runnable> mPrioritizedTasks;
|
private final Queue<Runnable> mPrioritizedTasks;
|
||||||
private boolean mIsShutdown;
|
private boolean mIsShutdown;
|
||||||
|
private final ThreadPoolExecutor mThreadPoolExecutor;
|
||||||
|
|
||||||
// The task which is running now.
|
// The task which is running now.
|
||||||
private Runnable mActive;
|
private Runnable mActive;
|
||||||
|
|
||||||
public PrioritizedSerialExecutor() {
|
public PrioritizedSerialExecutor() {
|
||||||
mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
|
mTasks = new ConcurrentLinkedQueue<Runnable>();
|
||||||
mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
|
mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>();
|
||||||
mIsShutdown = false;
|
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) {
|
public void execute(final Runnable r) {
|
||||||
synchronized(mLock) {
|
synchronized(mLock) {
|
||||||
if (!mIsShutdown) {
|
if (!mIsShutdown) {
|
||||||
mTasks.offer(r);
|
mTasks.offer(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
r.run();
|
||||||
|
} finally {
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
if (mActive == null) {
|
if (mActive == null) {
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
}
|
}
|
||||||
|
@ -74,45 +87,36 @@ public class PrioritizedSerialExecutor {
|
||||||
public void executePrioritized(final Runnable r) {
|
public void executePrioritized(final Runnable r) {
|
||||||
synchronized(mLock) {
|
synchronized(mLock) {
|
||||||
if (!mIsShutdown) {
|
if (!mIsShutdown) {
|
||||||
mPrioritizedTasks.offer(r);
|
mPrioritizedTasks.offer(new Runnable() {
|
||||||
if (mActive == null) {
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
r.run();
|
||||||
|
} finally {
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (mActive == null) {
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean fetchNextTasks() {
|
private boolean fetchNextTasksLocked() {
|
||||||
synchronized(mLock) {
|
mActive = mPrioritizedTasks.poll();
|
||||||
mActive = mPrioritizedTasks.poll();
|
if (mActive == null) {
|
||||||
if (mActive == null) {
|
mActive = mTasks.poll();
|
||||||
mActive = mTasks.poll();
|
|
||||||
}
|
|
||||||
return mActive != null;
|
|
||||||
}
|
}
|
||||||
|
return mActive != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleNext() {
|
private void scheduleNext() {
|
||||||
synchronized(mLock) {
|
synchronized(mLock) {
|
||||||
if (!fetchNextTasks()) {
|
if (fetchNextTasksLocked()) {
|
||||||
return;
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,29 +84,24 @@ public class UserHistoryDictionaryTests extends AndroidTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param checksContents if true, checks whether written words are actually in the dictionary
|
* @param checkContents if true, checks whether written words are actually in the dictionary
|
||||||
* or not.
|
* or not.
|
||||||
*/
|
*/
|
||||||
private void addAndWriteRandomWords(final String testFilenameSuffix, final int numberOfWords,
|
private void addAndWriteRandomWords(final String testFilenameSuffix, final int numberOfWords,
|
||||||
final Random random, final boolean checksContents) {
|
final Random random, final boolean checkContents) {
|
||||||
final List<String> words = generateWords(numberOfWords, random);
|
final List<String> words = generateWords(numberOfWords, random);
|
||||||
final UserHistoryPredictionDictionary dict =
|
final UserHistoryPredictionDictionary dict =
|
||||||
PersonalizationHelper.getUserHistoryPredictionDictionary(getContext(),
|
PersonalizationHelper.getUserHistoryPredictionDictionary(getContext(),
|
||||||
testFilenameSuffix /* locale */, mPrefs);
|
testFilenameSuffix /* locale */, mPrefs);
|
||||||
// Add random words to the user history dictionary.
|
// Add random words to the user history dictionary.
|
||||||
addToDict(dict, words);
|
addToDict(dict, words);
|
||||||
if (checksContents) {
|
if (checkContents) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(TimeUnit.MILLISECONDS.convert(5L, TimeUnit.SECONDS));
|
Thread.sleep(TimeUnit.MILLISECONDS.convert(5L, TimeUnit.SECONDS));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
}
|
}
|
||||||
// Limit word count to check when using a Java on memory dictionary.
|
for (int i = 0; i < numberOfWords; ++i) {
|
||||||
final int wordCountToCheck =
|
|
||||||
ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
|
|
||||||
numberOfWords : 10;
|
|
||||||
for (int i = 0; i < wordCountToCheck; ++i) {
|
|
||||||
final String word = words.get(i);
|
final String word = words.get(i);
|
||||||
// This may fail as long as we use tryLock on inserting the bigram words
|
|
||||||
assertTrue(dict.isInDictionaryForTests(word));
|
assertTrue(dict.isInDictionaryForTests(word));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue