| 1447 | |
| 1448 | template <class PropagatorStateType> |
| 1449 | void ExecutorState<PropagatorStateType>::Finish() { |
| 1450 | mu_.lock(); |
| 1451 | auto status = status_; |
| 1452 | auto done_cb = std::move(done_cb_); |
| 1453 | auto runner = std::move(runner_); |
| 1454 | mu_.unlock(); |
| 1455 | int64_t step_id = step_id_; |
| 1456 | CHECK(done_cb != nullptr); |
| 1457 | Device* device = immutable_state_.params().device; |
| 1458 | |
| 1459 | if (vlog_ && !status.ok() && VLOG_IS_ON(1)) { |
| 1460 | // Logs verbose information about the current state of active and pending |
| 1461 | // nodes in the propagator. |
| 1462 | propagator_.DumpState(); |
| 1463 | } |
| 1464 | |
| 1465 | // There are several potential race conditions below. To name a few: |
| 1466 | // 1. Even if the device's status is OK at the precise moment when |
| 1467 | // num_deferred_ops_ reaches 0, it could go bad before device->RefreshStatus() |
| 1468 | // is called below, caused by work enqueued onto the same device by other |
| 1469 | // concurrent ExecutorState objects. |
| 1470 | // 2. Some implementations of Device::RefreshStatus, such as |
| 1471 | // XlaDevice::RefreshStatus, may be inherently racy because it releases the |
| 1472 | // device mutex after a stream pointer is acquired and before the stream is |
| 1473 | // queried for status. |
| 1474 | // 3. It's the same for some implementations of Device::Sync, such as |
| 1475 | // XlaDevice::Sync. |
| 1476 | // |
| 1477 | // However, these race conditions are acceptable because a stream (and |
| 1478 | // therefore an XlaDevice) can only go from OK to not-OK, never the opposite, |
| 1479 | // which means we will at worst report errors when there isn't any, never the |
| 1480 | // opposite. |
| 1481 | |
| 1482 | // An early exit for devices don't allow sync on completion. Ops that run on |
| 1483 | // these devices should have used num_deferred_ops correctly to ensure the |
| 1484 | // device has finished all relevant work at this point. |
| 1485 | if (!device->AllowsSyncOnCompletion()) { |
| 1486 | status.Update(device->RefreshStatus()); |
| 1487 | if (!status.ok()) { |
| 1488 | // In device async execution mode, it's possible for device execution to |
| 1489 | // lag behind ExecutorState scheduling so much that this is the first |
| 1490 | // place a device execution error surfaces. |
| 1491 | // If so, all ExecutorState::NodeDone calls have already happened with OK |
| 1492 | // status. This is the last defense where StartCancel must be called to |
| 1493 | // abort all computation still running on any device. |
| 1494 | // TODO(b/124523000): Always call Finish in a separate thread, so even if |
| 1495 | // StartCancel blocks the current thread's execution, we won't encounter |
| 1496 | // deadlocks caused by inter-op thread exhaustion. |
| 1497 | if (rendezvous_) { |
| 1498 | rendezvous_->StartAbort(status); |
| 1499 | } |
| 1500 | if (cancellation_manager_) { |
| 1501 | cancellation_manager_->StartCancel(); |
| 1502 | } else if (collective_executor_) { |
| 1503 | // If there's cancellation_manager_, collective ops aborts |
| 1504 | // collective_executor_ upon cancellation; otherwise we need to abort |
| 1505 | // here. |
| 1506 | collective_executor_->StartAbort(status); |
nothing calls this directly
no test coverage detected