Skip to content

Commit ed49b03

Browse files
authored
Merge pull request #38608 from gottesmm/release/5.5-disable-flake
[5.5] Delete flaky tests and add back test coverage to other crasher tests
2 parents 9c0eb40 + 4d9aa67 commit ed49b03

File tree

4 files changed

+420
-918
lines changed

4 files changed

+420
-918
lines changed

test/Concurrency/Runtime/exclusivity.swift

Lines changed: 299 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ public func debugLog(_ s: String) {
5050
@available(SwiftStdlib 5.5, *)
5151
@main
5252
struct Runner {
53+
@MainActor
54+
@inline(never)
55+
static func withExclusiveAccessAsync<T, U>(to x: inout T, f: (inout T) async -> U) async -> U {
56+
await f(&x)
57+
}
58+
59+
@MainActor
60+
@inline(never)
61+
static func withExclusiveAccess<T, U>(to x: inout T, f: (inout T) -> U) -> U {
62+
f(&x)
63+
}
64+
5365
@inline(never)
5466
@MainActor
5567
static func doSomething() async { }
@@ -295,8 +307,8 @@ struct Runner {
295307
// First access begins here.
296308
await callCallee1()
297309
useGlobal(&global1) // We should not crash here since we cleaned up
298-
// the access in callCallee1 after we returned
299-
// from the await there.
310+
// the access in callCallee1 after we returned
311+
// from the await there.
300312
debugLog("==> Exit Main")
301313
}
302314

@@ -338,11 +350,294 @@ struct Runner {
338350
// First access begins here.
339351
await callCallee1()
340352
useGlobal(&global1) // We should not crash here since we cleaned up
341-
// the access in callCallee1 after we returned
342-
// from the await there.
353+
// the access in callCallee1 after we returned
354+
// from the await there.
343355
debugLog("==> Exit Main")
344356
}
345357

358+
// These are additional tests that used to be FileChecked but FileCheck
359+
// was too hard to use in a concurrent context.
360+
exclusivityTests.test("case1") { @MainActor in
361+
@inline(never)
362+
@Sendable func callee2(_ x: inout Int, _ y: inout Int, _ z: inout Int) -> Void {
363+
debugLog("==> Enter callee2")
364+
debugLog("==> Exit callee2")
365+
}
366+
367+
// We add an inline never here to make sure that we do not eliminate
368+
// the dynamic access after inlining.
369+
@MainActor
370+
@inline(never)
371+
func callee1() async -> () {
372+
debugLog("==> Enter callee1")
373+
let handle = Task { @MainActor in
374+
debugLog("==> Enter callee1 Closure")
375+
376+
// These accesses end before we await in the task.
377+
do {
378+
callee2(&global1, &global2, &global3)
379+
}
380+
let handle2 = Task { @MainActor in
381+
debugLog("==> Enter handle2!")
382+
debugLog("==> Exit handle2!")
383+
}
384+
await handle2.value
385+
debugLog("==> Exit callee1 Closure")
386+
}
387+
await handle.value
388+
debugLog("==> Exit callee1")
389+
}
390+
391+
debugLog("==> Enter 'testCase1'")
392+
await callee1()
393+
debugLog("==> Exit 'testCase1'")
394+
}
395+
396+
// Case 2: (F, F, T). In case 2, our task does not start with a live access
397+
// and nothing from the outside synchronous context, but does pop with a new
398+
// access.
399+
//
400+
// We use a suspend point and a withExclusiveAccessAsync(to:) to test this.
401+
exclusivityTests.test("case2.filecheck.nocrash") { @MainActor in
402+
debugLog("==> Enter 'testCase2'")
403+
404+
let handle = Task { @MainActor in
405+
debugLog("==> Inner Handle")
406+
await withExclusiveAccessAsync(to: &global1) { @MainActor (x: inout Int) async -> Void in
407+
let innerTaskHandle = Task { @MainActor in
408+
// Different task, shouldn't crash.
409+
withExclusiveAccess(to: &global1) { _ in
410+
debugLog("==> No crash!")
411+
}
412+
debugLog("==> End Inner Task Handle")
413+
}
414+
// This will cause us to serialize the access to global1. If
415+
// we had an access here, we would crash.
416+
await innerTaskHandle.value
417+
debugLog("==> After")
418+
}
419+
// Accessis over. We shouldn't crash here.
420+
withExclusiveAccess(to: &global1) { _ in
421+
debugLog("==> No crash!")
422+
}
423+
debugLog("==> Inner Handle: After exclusive access")
424+
}
425+
426+
await handle.value
427+
debugLog("==> After exclusive access")
428+
let handle2 = Task { @MainActor in
429+
debugLog("==> Enter handle2!")
430+
debugLog("==> Exit handle2!")
431+
}
432+
await handle2.value
433+
debugLog("==> Exit 'testCase2'")
434+
}
435+
436+
exclusivityTests.test("case2.filecheck.crash") { @MainActor in
437+
expectCrashLater(withMessage: "Fatal access conflict detected")
438+
debugLog("==> Enter 'testCase2'")
439+
440+
let handle = Task { @MainActor in
441+
debugLog("==> Inner Handle")
442+
await withExclusiveAccessAsync(to: &global1) { @MainActor (x: inout Int) async -> Void in
443+
let innerTaskHandle = Task { @MainActor in
444+
debugLog("==> End Inner Task Handle")
445+
}
446+
await innerTaskHandle.value
447+
// We will crash here if we properly brought back in the
448+
// access to global1 despite running code on a different
449+
// task.
450+
withExclusiveAccess(to: &global1) { _ in
451+
debugLog("==> Got a crash!")
452+
}
453+
debugLog("==> After")
454+
}
455+
debugLog("==> Inner Handle: After exclusive access")
456+
}
457+
458+
await handle.value
459+
debugLog("==> After exclusive access")
460+
let handle2 = Task { @MainActor in
461+
debugLog("==> Enter handle2!")
462+
debugLog("==> Exit handle2!")
463+
}
464+
await handle2.value
465+
debugLog("==> Exit 'testCase2'")
466+
}
467+
468+
// Case 5: (T,F,F). To test case 5, we use with exclusive access to to
469+
// create an exclusivity scope that goes over a suspension point. We are
470+
// interesting in the case where we return after the suspension point. That
471+
// push/pop is going to have our outer task bring in state and end it.
472+
//
473+
// CHECK-LABEL: ==> Enter 'testCase5'
474+
// CHECK: ==> Task: [[TASK:0x[0-9a-f]+]]
475+
// CHECK: Inserting new access: [[LLNODE:0x[a-z0-9]+]]
476+
// CHECK-NEXT: Tracking!
477+
// CHECK-NEXT: Access. Pointer: [[ACCESS:0x[a-z0-9]+]]
478+
// CHECK: Exiting Thread Local Context. Before Swizzle. Task: [[TASK]]
479+
// CHECK-NEXT: SwiftTaskThreadLocalContext: (FirstAccess,LastAccess): (0x0, 0x0)
480+
// CHECK-NEXT: Access. Pointer: [[ACCESS]]. PC:
481+
// CHECK: Exiting Thread Local Context. After Swizzle. Task: [[TASK]]
482+
// CHECK_NEXT: SwiftTaskThreadLocalContext: (FirstAccess,LastAccess): ([[LLNODE]], [[LLNODE]])
483+
// CHECK_NEXT: No Accesses.
484+
//
485+
// CHECK-NOT: Removing access:
486+
// CHECK: ==> End Inner Task Handle
487+
// CHECK: ==> After
488+
// CHECK: Removing access: [[LLNODE]]
489+
// CHECK: ==> After exclusive access
490+
// CHECK: Exiting Thread Local Context. Before Swizzle. Task: [[TASK]]
491+
// CHECK-NEXT: SwiftTaskThreadLocalContext: (FirstAccess,LastAccess): (0x0, 0x0)
492+
// CHECK-NEXT: No Accesses.
493+
// CHECK: Exiting Thread Local Context. After Swizzle. Task: [[TASK]]
494+
// CHECK-NEXT: SwiftTaskThreadLocalContext: (FirstAccess,LastAccess): (0x0, 0x0)
495+
// CHECK-NEXT: No Accesses.
496+
//
497+
// CHECK: ==> Exit 'testCase5'
498+
exclusivityTests.test("case5.filecheck") { @MainActor in
499+
debugLog("==> Enter 'testCase5'")
500+
501+
let outerHandle = Task { @MainActor in
502+
await withExclusiveAccessAsync(to: &global1) { @MainActor (x: inout Int) async -> Void in
503+
let innerTaskHandle = Task { @MainActor in
504+
debugLog("==> End Inner Task Handle")
505+
}
506+
await innerTaskHandle.value
507+
debugLog("==> After")
508+
}
509+
debugLog("==> After exclusive access")
510+
let handle2 = Task { @MainActor in
511+
debugLog("==> Enter handle2!")
512+
debugLog("==> Exit handle2!")
513+
}
514+
await handle2.value
515+
}
516+
await outerHandle.value
517+
debugLog("==> Exit 'testCase5'")
518+
}
519+
520+
exclusivityTests.test("case5.filecheck.crash") { @MainActor in
521+
expectCrashLater(withMessage: "Fatal access conflict detected")
522+
debugLog("==> Enter 'testCase5'")
523+
524+
let outerHandle = Task { @MainActor in
525+
await withExclusiveAccessAsync(to: &global1) { @MainActor (x: inout Int) async -> Void in
526+
let innerTaskHandle = Task { @MainActor in
527+
debugLog("==> End Inner Task Handle")
528+
}
529+
await innerTaskHandle.value
530+
debugLog("==> After")
531+
withExclusiveAccess(to: &global1) { _ in
532+
debugLog("==> Crash here")
533+
}
534+
}
535+
debugLog("==> After exclusive access")
536+
let handle2 = Task { @MainActor in
537+
debugLog("==> Enter handle2!")
538+
debugLog("==> Exit handle2!")
539+
}
540+
await handle2.value
541+
}
542+
await outerHandle.value
543+
debugLog("==> Exit 'testCase5'")
544+
}
545+
546+
// Case 6: (T, F, T). In case 6, our task starts with live accesses and is
547+
// popped with live accesses. There are no sync accesses.
548+
//
549+
// We test this by looking at the behavior of the runtime after we
550+
// finish executing handle2. In this case, we first check that things
551+
// just work normally and as a 2nd case perform a conflicting access to
552+
// make sure we crash.
553+
exclusivityTests.test("case6.filecheck") { @MainActor in
554+
let outerHandle = Task { @MainActor in
555+
let callee2 = { @MainActor (_ x: inout Int) -> Void in
556+
debugLog("==> Enter callee2")
557+
debugLog("==> Exit callee2")
558+
}
559+
560+
// We add an inline never here to make sure that we do not eliminate
561+
// the dynamic access after inlining.
562+
@MainActor
563+
@inline(never)
564+
func callee1(_ x: inout Int) async -> () {
565+
debugLog("==> Enter callee1")
566+
// This task is what prevents this example from crashing.
567+
let handle = Task { @MainActor in
568+
debugLog("==> Enter callee1 Closure")
569+
// Second access. Different Task so it is ok.
570+
await withExclusiveAccessAsync(to: &global1) {
571+
await callee2(&$0)
572+
}
573+
debugLog("==> Exit callee1 Closure")
574+
}
575+
await handle.value
576+
debugLog("==> callee1 after first await")
577+
// Force an await here so we can see that we properly swizzle.
578+
let handle2 = Task { @MainActor in
579+
debugLog("==> Enter handle2!")
580+
debugLog("==> Exit handle2!")
581+
}
582+
await handle2.value
583+
debugLog("==> Exit callee1")
584+
}
585+
586+
// First access begins here.
587+
await callee1(&global1)
588+
}
589+
debugLog("==> Enter 'testCase6'")
590+
await outerHandle.value
591+
debugLog("==> Exit 'testCase6'")
592+
}
593+
594+
exclusivityTests.test("case6.filecheck.crash") { @MainActor in
595+
expectCrashLater(withMessage: "Fatal access conflict detected")
596+
let outerHandle = Task { @MainActor in
597+
let callee2 = { @MainActor (_ x: inout Int) -> Void in
598+
debugLog("==> Enter callee2")
599+
debugLog("==> Exit callee2")
600+
}
601+
602+
// We add an inline never here to make sure that we do not eliminate
603+
// the dynamic access after inlining.
604+
@MainActor
605+
@inline(never)
606+
func callee1(_ x: inout Int) async -> () {
607+
debugLog("==> Enter callee1")
608+
// This task is what prevents this example from crashing.
609+
let handle = Task { @MainActor in
610+
debugLog("==> Enter callee1 Closure")
611+
// Second access. Different Task so it is ok.
612+
await withExclusiveAccessAsync(to: &global1) {
613+
await callee2(&$0)
614+
}
615+
debugLog("==> Exit callee1 Closure")
616+
}
617+
await handle.value
618+
debugLog("==> callee1 after first await")
619+
// Force an await here so we can see that we properly swizzle.
620+
let handle2 = Task { @MainActor in
621+
debugLog("==> Enter handle2!")
622+
debugLog("==> Exit handle2!")
623+
}
624+
await handle2.value
625+
// Make sure we brought back in the access to x so we crash
626+
// here.
627+
withExclusiveAccess(to: &global1) { _ in
628+
debugLog("==> Will crash here!")
629+
}
630+
debugLog("==> Exit callee1")
631+
}
632+
633+
// First access begins here.
634+
await callee1(&global1)
635+
}
636+
debugLog("==> Enter 'testCase6'")
637+
await outerHandle.value
638+
debugLog("==> Exit 'testCase6'")
639+
}
640+
346641
await runAllTestsAsync()
347642
}
348643
}

0 commit comments

Comments
 (0)