| 76 | } |
| 77 | |
| 78 | void stopServices() { |
| 79 | _started = false; |
| 80 | |
| 81 | Ref<TCPConnectionImpl> connectionToClose; |
| 82 | Ref<Thread> asioThread; |
| 83 | std::thread::id asioThreadId; |
| 84 | { |
| 85 | std::lock_guard<Mutex> lock(_mutex); |
| 86 | connectionToClose = _connection; |
| 87 | _connection = nullptr; |
| 88 | asioThread = _asioThread; |
| 89 | asioThreadId = _asioThreadId; |
| 90 | // Don't clear _asioThread/_asioThreadId yet: keeping them non-null |
| 91 | // prevents a concurrent startServices() from creating a new service |
| 92 | // while shutdown is still in progress. |
| 93 | } |
| 94 | |
| 95 | if (asioThread == nullptr) { |
| 96 | return; |
| 97 | } |
| 98 | |
| 99 | // If this is the asio thread (eg. connect failed), close and stop inline; |
| 100 | // posting would deadlock since we'd be waiting for our own handler. |
| 101 | if (std::this_thread::get_id() == asioThreadId) { |
| 102 | if (connectionToClose != nullptr) { |
| 103 | connectionToClose->close(Error("Disconnected")); |
| 104 | } |
| 105 | _ioService.stop(); |
| 106 | _work.reset(); |
| 107 | // Note: stale handlers will be drained in startServices() via |
| 108 | // reset() before the next run(). We can't drain here because |
| 109 | // we're still inside a handler on the asio thread; run() will |
| 110 | // return after this handler completes. |
| 111 | { |
| 112 | std::lock_guard<Mutex> lock(_mutex); |
| 113 | _asioThread = nullptr; |
| 114 | _asioThreadId = std::thread::id(); |
| 115 | } |
| 116 | return; |
| 117 | } |
| 118 | |
| 119 | // COMPOSER-5531: Close sockets on the Asio thread *before* stopping io_service |
| 120 | // because socket::close requires a valid service and will crash otherwise. |
| 121 | // Since the Asio thread owns the socket, post cleanup to the thread to prevent |
| 122 | // concurrent socket access; only Asio thread should access socket during closure |
| 123 | std::atomic<bool> shutdownDone{false}; |
| 124 | |
| 125 | _ioService.post([this, connectionToClose, &shutdownDone]() { |
| 126 | if (connectionToClose != nullptr) { |
| 127 | connectionToClose->close(Error("Disconnected")); |
| 128 | } |
| 129 | _ioService.stop(); |
| 130 | _work.reset(); |
| 131 | shutdownDone.store(true); |
| 132 | }); |
| 133 | |
| 134 | // join() blocks until run() returns: either because posted lambda |
| 135 | // called stop() or because run() exited due to an exception. |