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