|
22 | 22 | #include "../CompatibilityOverride/CompatibilityOverride.h"
|
23 | 23 | #include "swift/ABI/Actor.h"
|
24 | 24 | #include "swift/ABI/Task.h"
|
| 25 | +#include "TaskPrivate.h" |
25 | 26 | #include "swift/Basic/ListMerger.h"
|
26 | 27 | #include "swift/Concurrency/Actor.h"
|
27 | 28 | #include "swift/Runtime/AccessibleFunction.h"
|
28 | 29 | #include "swift/Runtime/Atomic.h"
|
| 30 | +#include "swift/Runtime/Bincompat.h" |
29 | 31 | #include "swift/Runtime/Casting.h"
|
30 | 32 | #include "swift/Runtime/DispatchShims.h"
|
31 | 33 | #include "swift/Threading/Mutex.h"
|
@@ -313,47 +315,193 @@ bool _task_serialExecutor_isSameExclusiveExecutionContext(
|
313 | 315 | const Metadata *selfType,
|
314 | 316 | const SerialExecutorWitnessTable *wtable);
|
315 | 317 |
|
| 318 | +// We currently still support "legacy mode" in which isCurrentExecutor is NOT |
| 319 | +// allowed to crash, because it is used to power "log warnings" data race |
| 320 | +// detector. This mode is going away in Swift 6, but until then we allow this. |
| 321 | +// This override exists primarily to be able to test both code-paths. |
| 322 | +enum IsCurrentExecutorCheckMode: unsigned { |
| 323 | + /// The default mode when an app was compiled against "new" enough SDK. |
| 324 | + /// It allows crashing in isCurrentExecutor, and calls into `checkIsolated`. |
| 325 | + Default_UseCheckIsolated_AllowCrash, |
| 326 | + /// Legacy mode; Primarily to support old applications which used data race |
| 327 | + /// detector with "warning" mode, which is no longer supported. When such app |
| 328 | + /// is re-compiled against a new SDK, it will see crashes in what was |
| 329 | + /// previously warnings; however, until until recompiled, warnings will be |
| 330 | + /// used, and `checkIsolated` cannot be invoked. |
| 331 | + Legacy_NoCheckIsolated_NonCrashing, |
| 332 | +}; |
| 333 | +static IsCurrentExecutorCheckMode isCurrentExecutorMode = |
| 334 | + Default_UseCheckIsolated_AllowCrash; |
| 335 | + |
| 336 | + |
| 337 | +// Shimming call to Swift runtime because Swift Embedded does not have |
| 338 | +// these symbols defined |
| 339 | +bool swift_bincompat_useLegacyNonCrashingExecutorChecks() { |
| 340 | +#if !SWIFT_CONCURRENCY_EMBEDDED |
| 341 | + swift::runtime::bincompat:: |
| 342 | + swift_bincompat_useLegacyNonCrashingExecutorChecks(); |
| 343 | +#endif |
| 344 | + return false; |
| 345 | +} |
| 346 | + |
| 347 | +// Check override of executor checking mode. |
| 348 | +static void checkIsCurrentExecutorMode(void *context) { |
| 349 | + auto useLegacyMode = |
| 350 | + swift_bincompat_useLegacyNonCrashingExecutorChecks(); |
| 351 | + |
| 352 | + // Potentially, override the platform detected mode, primarily used in tests. |
| 353 | +#if SWIFT_STDLIB_HAS_ENVIRON |
| 354 | + const char *modeStr = getenv("SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE"); |
| 355 | + if (!modeStr) |
| 356 | + return; |
| 357 | + |
| 358 | + if (strcmp(modeStr, "nocrash") == 0) { |
| 359 | + useLegacyMode = Legacy_NoCheckIsolated_NonCrashing; |
| 360 | + } else if (strcmp(modeStr, "crash") == 0) { |
| 361 | + useLegacyMode = Default_UseCheckIsolated_AllowCrash; |
| 362 | + } // else, just use the platform detected mode |
| 363 | +#endif // SWIFT_STDLIB_HAS_ENVIRON |
| 364 | + isCurrentExecutorMode = useLegacyMode ? Legacy_NoCheckIsolated_NonCrashing |
| 365 | + : Default_UseCheckIsolated_AllowCrash; |
| 366 | +} |
| 367 | + |
316 | 368 | SWIFT_CC(swift)
|
317 |
| -static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef executor) { |
| 369 | +static bool swift_task_isCurrentExecutorImpl(SerialExecutorRef expectedExecutor) { |
318 | 370 | auto current = ExecutorTrackingInfo::current();
|
319 | 371 |
|
| 372 | + // To support old applications on apple platforms which assumed this call |
| 373 | + // does not crash, try to use a more compatible mode for those apps. |
| 374 | + static swift::once_t checkModeToken; |
| 375 | + swift::once(checkModeToken, checkIsCurrentExecutorMode, nullptr); |
| 376 | + |
320 | 377 | if (!current) {
|
321 |
| - // TODO(ktoso): checking the "is main thread" is not correct, main executor can be not main thread, relates to rdar://106188692 |
322 |
| - return executor.isMainExecutor() && isExecutingOnMainThread(); |
| 378 | + // We have no current executor, i.e. we are running "outside" of Swift |
| 379 | + // Concurrency. We could still be running on a thread/queue owned by |
| 380 | + // the expected executor however, so we need to try a bit harder before |
| 381 | + // we fail. |
| 382 | + |
| 383 | + // Are we expecting the main executor and are using the main thread? |
| 384 | + if (expectedExecutor.isMainExecutor() && isExecutingOnMainThread()) { |
| 385 | + // Due to compatibility with pre-checkIsolated code, we cannot remove |
| 386 | + // this special handling. CheckIsolated can handle this if the expected |
| 387 | + // executor is the main queue / main executor, however, if we cannot call |
| 388 | + // checkIsolated we cannot rely on it to handle this. |
| 389 | + // TODO: consider removing this branch when `useCrashingCheckIsolated=true` |
| 390 | + return true; |
| 391 | + } |
| 392 | + |
| 393 | + // Otherwise, as last resort, let the expected executor check using |
| 394 | + // external means, as it may "know" this thread is managed by it etc. |
| 395 | + if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) { |
| 396 | + swift_task_checkIsolated(expectedExecutor); |
| 397 | + // checkIsolated did not crash, so we are on the right executor, after all! |
| 398 | + return true; |
| 399 | + } |
| 400 | + |
| 401 | + assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing); |
| 402 | + return false; |
323 | 403 | }
|
324 | 404 |
|
325 |
| - auto currentExecutor = current->getActiveExecutor(); |
326 |
| - if (currentExecutor == executor) { |
| 405 | + SerialExecutorRef currentExecutor = current->getActiveExecutor(); |
| 406 | + |
| 407 | + // Fast-path: the executor is exactly the same memory address; |
| 408 | + // We assume executors do not come-and-go appearing under the same address, |
| 409 | + // and treat pointer equality of executors as good enough to assume the executor. |
| 410 | + if (currentExecutor == expectedExecutor) { |
| 411 | + return true; |
| 412 | + } |
| 413 | + |
| 414 | + // Fast-path, specialize the common case of comparing two main executors. |
| 415 | + if (currentExecutor.isMainExecutor() && expectedExecutor.isMainExecutor()) { |
327 | 416 | return true;
|
328 | 417 | }
|
329 | 418 |
|
330 |
| - if (executor.isComplexEquality()) { |
| 419 | + // If the expected executor is "default" then we should have matched |
| 420 | + // by pointer equality already with the current executor. |
| 421 | + if (expectedExecutor.isDefaultActor()) { |
| 422 | + // If the expected executor is a default actor, it makes no sense to try |
| 423 | + // the 'checkIsolated' call, it must be equal to the other actor, or it is |
| 424 | + // not the same isolation domain. |
| 425 | + swift_Concurrency_fatalError(0, "Incorrect actor executor assumption"); |
| 426 | + return false; |
| 427 | + } |
| 428 | + |
| 429 | + if (expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor()) { |
| 430 | + // TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, otherwise messages will be sub-par and hard to address |
| 431 | + swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected MainActor executor"); |
| 432 | + return false; |
| 433 | + } else if (!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor()) { |
| 434 | + // TODO: Invoke checkIsolated() on "main" SerialQueue once it implements `checkIsolated`, otherwise messages will be sub-par and hard to address |
| 435 | + swift_Concurrency_fatalError(0, "Incorrect actor executor assumption; Expected not-MainActor executor"); |
| 436 | + return false; |
| 437 | + } |
| 438 | + |
| 439 | + if (expectedExecutor.isComplexEquality()) { |
331 | 440 | if (!swift_compareWitnessTables(
|
332 | 441 | reinterpret_cast<const WitnessTable*>(currentExecutor.getSerialExecutorWitnessTable()),
|
333 |
| - reinterpret_cast<const WitnessTable*>(executor.getSerialExecutorWitnessTable()))) { |
| 442 | + reinterpret_cast<const WitnessTable*>(expectedExecutor.getSerialExecutorWitnessTable()))) { |
334 | 443 | // different witness table, we cannot invoke complex equality call
|
335 | 444 | return false;
|
336 | 445 | }
|
| 446 | + |
337 | 447 | // Avoid passing nulls to Swift for the isSame check:
|
338 |
| - if (!currentExecutor.getIdentity() || !executor.getIdentity()) { |
| 448 | + if (!currentExecutor.getIdentity() || !expectedExecutor.getIdentity()) { |
339 | 449 | return false;
|
340 | 450 | }
|
341 | 451 |
|
342 |
| - return _task_serialExecutor_isSameExclusiveExecutionContext( |
| 452 | + auto result = _task_serialExecutor_isSameExclusiveExecutionContext( |
343 | 453 | currentExecutor.getIdentity(),
|
344 |
| - executor.getIdentity(), |
| 454 | + expectedExecutor.getIdentity(), |
345 | 455 | swift_getObjectType(currentExecutor.getIdentity()),
|
346 |
| - executor.getSerialExecutorWitnessTable()); |
| 456 | + expectedExecutor.getSerialExecutorWitnessTable()); |
| 457 | + |
| 458 | + return result; |
| 459 | + } |
| 460 | + |
| 461 | + // This provides a last-resort check by giving the expected SerialExecutor the |
| 462 | + // chance to perform a check using some external knowledge if perhaps we are, |
| 463 | + // after all, on this executor, but the Swift concurrency runtime was just not |
| 464 | + // aware. |
| 465 | + // |
| 466 | + // Unless handled in `swift_task_checkIsolated` directly, this should call |
| 467 | + // through to the executor's `SerialExecutor.checkIsolated`. |
| 468 | + // |
| 469 | + // This call is expected to CRASH, unless it has some way of proving that |
| 470 | + // we're actually indeed running on this executor. |
| 471 | + // |
| 472 | + // For example, when running outside of Swift concurrency tasks, but trying to |
| 473 | + // `MainActor.assumeIsolated` while executing DIRECTLY on the main dispatch |
| 474 | + // queue, this allows Dispatch to check for this using its own tracking |
| 475 | + // mechanism, and thus allow the assumeIsolated to work correctly, even though |
| 476 | + // the code executing is not even running inside a Task. |
| 477 | + // |
| 478 | + // Note that this only works because the closure in assumeIsolated is |
| 479 | + // synchronous, and will not cause suspensions, as that would require the |
| 480 | + // presence of a Task. |
| 481 | + // compat_invoke_swift_task_checkIsolated(expectedExecutor); |
| 482 | + if (isCurrentExecutorMode == Default_UseCheckIsolated_AllowCrash) { |
| 483 | + swift_task_checkIsolated(expectedExecutor); |
| 484 | + // The checkIsolated call did not crash, so we are on the right executor. |
| 485 | + return true; |
347 | 486 | }
|
348 | 487 |
|
| 488 | + // Using legacy mode, if no explicit executor match worked, we assume `false` |
| 489 | + assert(isCurrentExecutorMode == Legacy_NoCheckIsolated_NonCrashing); |
349 | 490 | return false;
|
350 | 491 | }
|
351 | 492 |
|
352 | 493 | /// Logging level for unexpected executors:
|
353 | 494 | /// 0 - no logging
|
354 | 495 | /// 1 - warn on each instance
|
355 | 496 | /// 2 - fatal error
|
356 |
| -static unsigned unexpectedExecutorLogLevel = 2; |
| 497 | +/// |
| 498 | +/// NOTE: The default behavior on Apple platforms depends on the SDK version |
| 499 | +/// an application was linked to. Since Swift 6 the default is to crash, |
| 500 | +/// and the logging behavior is no longer available. |
| 501 | +static unsigned unexpectedExecutorLogLevel = |
| 502 | + swift_bincompat_useLegacyNonCrashingExecutorChecks() |
| 503 | + ? 1 // legacy apps default to the logging mode, and cannot use `checkIsolated` |
| 504 | + : 2; // new apps will only crash upon concurrency violations, and will call into `checkIsolated` |
357 | 505 |
|
358 | 506 | static void checkUnexpectedExecutorLogLevel(void *context) {
|
359 | 507 | #if SWIFT_STDLIB_HAS_ENVIRON
|
|
0 commit comments