• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.util;
18 
19 import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX;
20 import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX;
21 
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import android.os.Handler;
26 import android.os.HandlerThread;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.concurrent.Semaphore;
38 import java.util.concurrent.TimeUnit;
39 
40 /**
41  * Event processor for reliably reproducing multithreaded apps race conditions in tests.
42  *
43  * The app notifies us about “events” that happen in its threads. The race condition test runs the
44  * test action multiple times (aka iterations), trying to generate all possible permutations of
45  * these events. It keeps a set of all seen event sequences and steers the execution towards
46  * executing events in previously unseen order. It does it by postponing execution of threads that
47  * would lead to an already seen sequence.
48  *
49  * If an event A occurs before event B in the sequence, this is how execution order looks like:
50  * Events: ... A ... B ...
51  * Events and instructions, guaranteed order:
52  * (instructions executed prior to A) A ... B (instructions executed after B)
53  *
54  * Each iteration has 3 parts (phases).
55  * Phase 1. Picking a previously seen event subsequence that we believe can have previously unseen
56  * continuations. Reproducing this sequence by pausing threads that would lead to other sequences.
57  * Phase 2. Trying to generate previously unseen continuation of the sequence from Phase 1. We need
58  * one new event after that sequence. All threads leading to seen continuations will be postponed
59  * for some short period of time. The phase ends once the new event is registered, or after the
60  * period of time ends (in which case we declare that the sequence can’t have new continuations).
61  * Phase 3. Releasing all threads and letting the test iteration run till its end.
62  *
63  * The iterations end when all seen paths have been declared “uncontinuable”.
64  *
65  * When we register event XXX:enter, we hold all other events until we register XXX:exit.
66  */
67 public class RaceConditionReproducer implements RaceConditionTracker.EventProcessor {
68     private static final String TAG = "RaceConditionReproducer";
69     private static final long SHORT_TIMEOUT_MS = 2000;
70     private static final long LONG_TIMEOUT_MS = 60000;
71     // Handler used to resume postponed events.
72     private static final Handler POSTPONED_EVENT_RESUME_HANDLER = createEventResumeHandler();
73 
createEventResumeHandler()74     private static Handler createEventResumeHandler() {
75         final HandlerThread thread = new HandlerThread("RaceConditionEventResumer");
76         thread.start();
77         return new Handler(thread.getLooper());
78     }
79 
80     /**
81      * Event in a particular sequence of events. A node in the prefix tree of all seen event
82      * sequences.
83      */
84     private class EventNode {
85         // Events that were seen just after this event.
86         private final Map<String, EventNode> mNextEvents = new HashMap<>();
87         // Whether we believe that further iterations will not be able to add more events to
88         // mNextEvents.
89         private boolean mStoppedAddingChildren = true;
90 
debugDump(StringBuilder sb, int indent, String name)91         private void debugDump(StringBuilder sb, int indent, String name) {
92             for (int i = 0; i < indent; ++i) sb.append('.');
93             sb.append(!mStoppedAddingChildren ? "+" : "-");
94             sb.append(" : ");
95             sb.append(name);
96             if (mLastRegisteredEvent == this) sb.append(" <");
97             sb.append('\n');
98 
99             for (String key : mNextEvents.keySet()) {
100                 mNextEvents.get(key).debugDump(sb, indent + 2, key);
101             }
102         }
103 
104         /** Number of leaves in the subtree with this node as a root. */
numberOfLeafNodes()105         private int numberOfLeafNodes() {
106             if (mNextEvents.isEmpty()) return 1;
107 
108             int leaves = 0;
109             for (String event : mNextEvents.keySet()) {
110                 leaves += mNextEvents.get(event).numberOfLeafNodes();
111             }
112             return leaves;
113         }
114 
115         /**
116          * Whether we believe that further iterations will not be able add nodes to the subtree with
117          * this node as a root.
118          */
stoppedAddingChildrenToTree()119         private boolean stoppedAddingChildrenToTree() {
120             if (!mStoppedAddingChildren) return false;
121 
122             for (String event : mNextEvents.keySet()) {
123                 if (!mNextEvents.get(event).stoppedAddingChildrenToTree()) return false;
124             }
125             return true;
126         }
127 
128         /**
129          * In the subtree with this node as a root, tries finding a node where we may have a
130          * chance to add new children.
131          * If succeeds, returns true and fills 'path' with the sequence of events to that node;
132          * otherwise returns false.
133          */
populatePathToGrowthPoint(List<String> path)134         private boolean populatePathToGrowthPoint(List<String> path) {
135             for (String event : mNextEvents.keySet()) {
136                 if (mNextEvents.get(event).populatePathToGrowthPoint(path)) {
137                     path.add(0, event);
138                     return true;
139                 }
140             }
141             if (!mStoppedAddingChildren) {
142                 // Mark that we have finished adding children. It will remain true if no new
143                 // children are added, or will be set to false upon adding a new child.
144                 mStoppedAddingChildren = true;
145                 return true;
146             }
147             return false;
148         }
149     }
150 
151     // Starting point of all event sequences; the root of the prefix tree representation all
152     // sequences generated by test iterations. A test iteration can add nodes int it.
153     private EventNode mRoot = new EventNode();
154     // During a test iteration, the last event that was registered.
155     private EventNode mLastRegisteredEvent;
156     // Length of the current sequence of registered events for the current test iteration.
157     private int mRegisteredEventCount = 0;
158     // During the first part of a test iteration, we go to a specific node under mRoot by
159     // 'playing back' mSequenceToFollow. During this part, all events that don't belong to this
160     // sequence get postponed.
161     private List<String> mSequenceToFollow = new ArrayList<>();
162     // Collection of events that got postponed, with corresponding wait objects used to let them go.
163     private Map<String, Semaphore> mPostponedEvents = new HashMap<>();
164     // Callback to run by POSTPONED_EVENT_RESUME_HANDLER, used to let go of all currently
165     // postponed events.
166     private Runnable mResumeAllEventsCallback;
167     // String representation of the sequence of events registered so far for the current test
168     // iteration. After registering any event, we output it to the log. The last output before
169     // the test failure can be later played back to reliable reproduce the exact sequence of
170     // events that broke the test.
171     // Format: EV1|EV2|...\EVN
172     private StringBuilder mCurrentSequence;
173     // When not null, we are in a repro mode. We run only one test iteration, and are trying to
174     // reproduce the event sequence represented by this string. The format is same as for
175     // mCurrentSequence.
176     private final String mReproString;
177 
178     /* Constructor for a normal test. */
RaceConditionReproducer()179     public RaceConditionReproducer() {
180         mReproString = null;
181     }
182 
183     /**
184      * Constructor for reliably reproducing a race condition failure. The developer should find in
185      * the log the latest "Repro sequence:" record and locally modify the test by passing that
186      * string to the constructor. Running the test will have only one iteration that will reliably
187      * "play back" that sequence.
188      */
RaceConditionReproducer(String reproString)189     public RaceConditionReproducer(String reproString) {
190         mReproString = reproString;
191     }
192 
RaceConditionReproducer(String... reproSequence)193     public RaceConditionReproducer(String... reproSequence) {
194         this(String.join("|", reproSequence));
195     }
196 
getCurrentSequenceString()197     public synchronized String getCurrentSequenceString() {
198         return mCurrentSequence.toString();
199     }
200 
201     /**
202      * Starts a new test iteration. Events reported via RaceConditionTracker.onEvent before this
203      * call will be ignored.
204      */
startIteration()205     public synchronized void startIteration() {
206         mLastRegisteredEvent = mRoot;
207         mRegisteredEventCount = 0;
208         mCurrentSequence = new StringBuilder();
209         Log.d(TAG, "Repro sequence: " + mCurrentSequence);
210         mSequenceToFollow = mReproString != null ?
211                 parseReproString(mReproString) : generateSequenceToFollowLocked();
212         Log.e(TAG, "---- Start of iteration; state:\n" + dumpStateLocked());
213         checkIfCompletedSequenceToFollowLocked();
214         RaceConditionTracker.setEventProcessor(this);
215     }
216 
217     /**
218      * Ends a new test iteration. Events reported via RaceConditionTracker.onEvent after this call
219      * will be ignored.
220      * Returns whether we need more iterations.
221      */
finishIteration()222     public synchronized boolean finishIteration() {
223         RaceConditionTracker.setEventProcessor(null);
224         runResumeAllEventsCallbackLocked();
225         assertTrue("Non-empty postponed events", mPostponedEvents.isEmpty());
226         assertTrue("Last registered event is :enter", lastEventAsEnter() == null);
227 
228         // No events came after mLastRegisteredEvent. It doesn't make sense to come to it again
229         // because we won't see new continuations.
230         mLastRegisteredEvent.mStoppedAddingChildren = true;
231         Log.e(TAG, "---- End of iteration; state:\n" + dumpStateLocked());
232         if (mReproString != null) {
233             assertTrue("Repro mode: failed to reproduce the sequence",
234                     mCurrentSequence.toString().startsWith(mReproString));
235         }
236         // If we are in a repro mode, we need only one iteration. Otherwise, continue if the tree
237         // has prospective growth points.
238         return mReproString == null && !mRoot.stoppedAddingChildrenToTree();
239     }
240 
parseReproString(String reproString)241     private static List<String> parseReproString(String reproString) {
242         return Arrays.asList(reproString.split("\\|"));
243     }
244 
245     /**
246      * Called when the app issues an event.
247      */
248     @Override
onEvent(String event)249     public void onEvent(String event) {
250         final Semaphore waitObject = tryRegisterEvent(event);
251         if (waitObject != null) {
252             waitUntilCanRegister(event, waitObject);
253         }
254     }
255 
256     /**
257      * Returns whether the last event was not an XXX:enter, or this event is a matching XXX:exit.
258      */
canRegisterEventNowLocked(String event)259     private boolean canRegisterEventNowLocked(String event) {
260         final String lastEventAsEnter = lastEventAsEnter();
261         final String thisEventAsExit = eventAsExit(event);
262 
263         if (lastEventAsEnter != null) {
264             if (!lastEventAsEnter.equals(thisEventAsExit)) {
265                 assertTrue("YYY:exit after XXX:enter", thisEventAsExit == null);
266                 // Last event was :enter, but this event is not :exit.
267                 return false;
268             }
269         } else {
270             // Previous event was not :enter.
271             assertTrue(":exit after a non-enter event", thisEventAsExit == null);
272         }
273         return true;
274     }
275 
276     /**
277      * Registers an event issued by the app and returns null or decides that the event must be
278      * postponed, and returns an object to wait on.
279      */
tryRegisterEvent(String event)280     private synchronized Semaphore tryRegisterEvent(String event) {
281         Log.d(TAG, "Event issued by the app: " + event);
282 
283         if (!canRegisterEventNowLocked(event)) {
284             return createWaitObjectForPostponedEventLocked(event);
285         }
286 
287         if (mRegisteredEventCount < mSequenceToFollow.size()) {
288             // We are in the first part of the iteration. We only register events that follow the
289             // mSequenceToFollow and postponing all other events.
290             if (event.equals(mSequenceToFollow.get(mRegisteredEventCount))) {
291                 // The event is the next one expected in the sequence. Register it.
292                 registerEventLocked(event);
293 
294                 // If there are postponed events that could continue the sequence, register them.
295                 while (mRegisteredEventCount < mSequenceToFollow.size() &&
296                         mPostponedEvents.containsKey(
297                                 mSequenceToFollow.get(mRegisteredEventCount))) {
298                     registerPostponedEventLocked(mSequenceToFollow.get(mRegisteredEventCount));
299                 }
300 
301                 // Perhaps we just completed the required sequence...
302                 checkIfCompletedSequenceToFollowLocked();
303             } else {
304                 // The event is not the next one in the sequence. Postpone it.
305                 return createWaitObjectForPostponedEventLocked(event);
306             }
307         } else if (mRegisteredEventCount == mSequenceToFollow.size()) {
308             // The second phase of the iteration. We have just registered the whole
309             // mSequenceToFollow, and want to add previously not seen continuations for the last
310             // node in the sequence aka 'growth point'.
311             if (!mLastRegisteredEvent.mNextEvents.containsKey(event) || mReproString != null) {
312                 // The event was never seen as a continuation for the current node.
313                 // Or we are in repro mode, in which case we are not in business of generating
314                 // new sequences after we've played back the required sequence.
315                 // Register it immediately.
316                 registerEventLocked(event);
317             } else {
318                 // The event was seen as a continuation for the current node. Postpone it, hoping
319                 // that a new event will come from other threads.
320                 return createWaitObjectForPostponedEventLocked(event);
321             }
322         } else {
323             // The third phase of the iteration. We are past the growth point and register
324             // everything that comes.
325             registerEventLocked(event);
326             // Register events that may have been postponed while waiting for an :exit event
327             // during the third phase. We don't do this if just registered event is :enter.
328             if (eventAsEnter(event) == null && mRegisteredEventCount > mSequenceToFollow.size()) {
329                 registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
330             }
331         }
332         return null;
333     }
334 
335     /** Called when there are chances that we just have registered the whole mSequenceToFollow. */
checkIfCompletedSequenceToFollowLocked()336     private void checkIfCompletedSequenceToFollowLocked() {
337         if (mRegisteredEventCount == mSequenceToFollow.size()) {
338             // We just entered the second phase of the iteration. We have just registered the
339             // whole mSequenceToFollow, and want to add previously not seen continuations for the
340             // last node in the sequence aka 'growth point'. All seen continuations will be
341             // postponed for SHORT_TIMEOUT_MS. At the end of this time period, we'll let them go.
342             scheduleResumeAllEventsLocked();
343 
344             // Among the events that were postponed during the first stage, there may be an event
345             // that wasn't seen after the current. If so, register it immediately because this
346             // creates a new sequence.
347             final Set<String> keys = new HashSet<>(mPostponedEvents.keySet());
348             keys.removeAll(mLastRegisteredEvent.mNextEvents.keySet());
349             if (!keys.isEmpty()) {
350                 registerPostponedEventLocked(keys.iterator().next());
351             }
352         }
353     }
354 
createWaitObjectForPostponedEventLocked(String event)355     private Semaphore createWaitObjectForPostponedEventLocked(String event) {
356         final Semaphore waitObject = new Semaphore(0);
357         assertTrue("Event already postponed: " + event, !mPostponedEvents.containsKey(event));
358         mPostponedEvents.put(event, waitObject);
359         return waitObject;
360     }
361 
waitUntilCanRegister(String event, Semaphore waitObject)362     private void waitUntilCanRegister(String event, Semaphore waitObject) {
363         try {
364             assertTrue("Never registered event: " + event,
365                     waitObject.tryAcquire(LONG_TIMEOUT_MS, TimeUnit.MILLISECONDS));
366         } catch (InterruptedException e) {
367             fail("Wait was interrupted");
368         }
369     }
370 
371     /** Schedules resuming all postponed events after SHORT_TIMEOUT_MS */
scheduleResumeAllEventsLocked()372     private void scheduleResumeAllEventsLocked() {
373         assertTrue(mResumeAllEventsCallback == null);
374         mResumeAllEventsCallback = this::allEventsResumeCallback;
375         POSTPONED_EVENT_RESUME_HANDLER.postDelayed(mResumeAllEventsCallback, SHORT_TIMEOUT_MS);
376     }
377 
allEventsResumeCallback()378     private synchronized void allEventsResumeCallback() {
379         assertTrue("In callback, but callback is not set", mResumeAllEventsCallback != null);
380         mResumeAllEventsCallback = null;
381         registerPostponedEventsLocked(new HashSet<>(mPostponedEvents.keySet()));
382     }
383 
registerPostponedEventsLocked(Collection<String> events)384     private void registerPostponedEventsLocked(Collection<String> events) {
385         for (String event : events) {
386             registerPostponedEventLocked(event);
387             if (eventAsEnter(event) != null) {
388                 // Once :enter is registered, switch to waiting for :exit to come. Won't register
389                 // other postponed events.
390                 break;
391             }
392         }
393     }
394 
registerPostponedEventLocked(String event)395     private void registerPostponedEventLocked(String event) {
396         mPostponedEvents.remove(event).release();
397         registerEventLocked(event);
398     }
399 
400     /**
401      * If the last registered event was XXX:enter, returns XXX, otherwise, null.
402      */
lastEventAsEnter()403     private String lastEventAsEnter() {
404         return eventAsEnter(mCurrentSequence.substring(mCurrentSequence.lastIndexOf("|") + 1));
405     }
406 
407     /**
408      * If the event is XXX:postfix, returns XXX, otherwise, null.
409      */
prefixFromPostfixedEvent(String event, String postfix)410     private static String prefixFromPostfixedEvent(String event, String postfix) {
411         final int columnPos = event.indexOf(':');
412         if (columnPos != -1 && postfix.equals(event.substring(columnPos + 1))) {
413             return event.substring(0, columnPos);
414         }
415         return null;
416     }
417 
418     /**
419      * If the event is XXX:enter, returns XXX, otherwise, null.
420      */
eventAsEnter(String event)421     private static String eventAsEnter(String event) {
422         return prefixFromPostfixedEvent(event, ENTER_POSTFIX);
423     }
424 
425     /**
426      * If the event is XXX:exit, returns XXX, otherwise, null.
427      */
eventAsExit(String event)428     private static String eventAsExit(String event) {
429         return prefixFromPostfixedEvent(event, EXIT_POSTFIX);
430     }
431 
registerEventLocked(String event)432     private void registerEventLocked(String event) {
433         assertTrue(canRegisterEventNowLocked(event));
434 
435         Log.d(TAG, "Actually registering event: " + event);
436         EventNode next = mLastRegisteredEvent.mNextEvents.get(event);
437         if (next == null) {
438             // This event wasn't seen after mLastRegisteredEvent.
439             next = new EventNode();
440             mLastRegisteredEvent.mNextEvents.put(event, next);
441             // The fact that we've added a new event after the previous one means that the
442             // previous event is still a growth point, unless this event is :exit, which means
443             // that the previous event is :enter.
444             mLastRegisteredEvent.mStoppedAddingChildren = eventAsExit(event) != null;
445         }
446 
447         mLastRegisteredEvent = next;
448         mRegisteredEventCount++;
449 
450         if (mCurrentSequence.length() > 0) mCurrentSequence.append("|");
451         mCurrentSequence.append(event);
452         Log.d(TAG, "Repro sequence: " + mCurrentSequence);
453     }
454 
runResumeAllEventsCallbackLocked()455     private void runResumeAllEventsCallbackLocked() {
456         if (mResumeAllEventsCallback != null) {
457             POSTPONED_EVENT_RESUME_HANDLER.removeCallbacks(mResumeAllEventsCallback);
458             mResumeAllEventsCallback.run();
459         }
460     }
461 
dumpStateLocked()462     private CharSequence dumpStateLocked() {
463         StringBuilder sb = new StringBuilder();
464 
465         sb.append("Sequence to follow: ");
466         for (String event : mSequenceToFollow) sb.append(" " + event);
467         sb.append(".\n");
468         sb.append("Registered event count: " + mRegisteredEventCount);
469 
470         sb.append("\nPostponed events: ");
471         for (String event : mPostponedEvents.keySet()) sb.append(" " + event);
472         sb.append(".");
473 
474         sb.append("\nNodes: \n");
475         mRoot.debugDump(sb, 0, "");
476         return sb;
477     }
478 
numberOfLeafNodes()479     public int numberOfLeafNodes() {
480         return mRoot.numberOfLeafNodes();
481     }
482 
generateSequenceToFollowLocked()483     private List<String> generateSequenceToFollowLocked() {
484         ArrayList<String> sequence = new ArrayList<>();
485         mRoot.populatePathToGrowthPoint(sequence);
486         return sequence;
487     }
488 }
489