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