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