two-headBoy commented on code in PR #3595:
URL: https://github.com/apache/brpc/pull/3595#discussion_r4246993883


##########
src/brpc/selective_channel.cpp:
##########
@@ -197,19 +201,33 @@ int ChannelBalancer::AddChannel(ChannelBase* sub_channel,
     options.health_check_interval_s = FLAGS_channel_check_interval;
 
     if (Socket::Create(options, &sock_id) != 0) {
-        delete sub_chan;
+        // On every creation-failure path Socket recycles synchronously and 
runs
+        // SubChannel::BeforeRecycle, which deletes chan iff OWNS_CHANNEL and
+        // then deletes sub_chan. Do NOT delete here: that double-frees.
         LOG(ERROR) << "Fail to create fake socket for sub channel";
         return -1;
     }
     SocketUniquePtr ptr;
     int rc = Socket::AddressFailedAsWell(sock_id, &ptr);
     if (rc < 0) {
         LOG(ERROR) << "Fail to address SocketId=" << sock_id;
+        // The fake Socket was created (holding the HC-related reference) but
+        // cannot be addressed. Recycle it so that sub_chan, and sub_channel
+        // when OWNS_CHANNEL, are deleted in SubChannel::BeforeRecycle.
+        SocketUniquePtr fail_ptr;
+        if (Socket::AddressFailedAsWell(sock_id, &fail_ptr) >= 0) {

Review Comment:
   Correct — that second`AddressFailedAsWell` can never succeed (`Create` just 
returned 0 holding the additional reference; once the first call returns -1 the 
slot is version-mismatched, so the repeat also returns -1 and`fail_ptr` is 
never set). Removed the dead block and its comment.`rc < 0` means the slot 
version advanced past`id_ver+1` , i.e. the socket was already`SetFailed` 
/recycled, so`BeforeRecycle` has already cleaned up`sub_chan` ; the branch now 
just logs and returns.



##########
src/brpc/socket.cpp:
##########
@@ -722,7 +722,10 @@ int Socket::Create(const SocketOptions& options, SocketId* 
id) {
 int Socket::OnCreated(const SocketOptions& options) {
     if (_io_event.Init((void*)id()) != 0) {
         LOG(ERROR) << "Fail to init IOEvent";
-        SetFailed(ENOMEM, "%s", "Fail to init IOEvent");
+        // VersionedRefWithId::Create() performs the failure transition after
+        // OnCreated() returns an error; attach the user before returning so
+        // recycling invokes SocketUser::BeforeRecycle.
+        _user = options.user;

Review Comment:
   Agreed, and this is the key insight that drove the fix. Attaching`_user` 
here is wrong not just because of the two caller symptoms but 
because`BeforeRecycled` would run on a half-constructed socket (`_transport` 
not created,`_id_wait_list` not initialized). Rather than make`BeforeRecycled` 
safe for partial construction (a separate, larger change), the`_user` 
attachment is dropped from the failure path entirely — it is assigned only at 
the end of`OnCreated` on success. That also resolves your "cannot be decided at 
the call site" point: with`_user` attached only on success,`Socket::Create` 
failure never triggers`BeforeRecycle` , so the contract becomes uniform — 
callers always own cleanup when`Create` fails — and`AddChannel` can use a 
single unconditional cleanup path with no ambiguity. (Making`BeforeRecycled` 
itself robust to a partially-constructed socket is left out of scope as you 
suggested.)



##########
src/brpc/selective_channel.cpp:
##########
@@ -197,19 +201,33 @@ int ChannelBalancer::AddChannel(ChannelBase* sub_channel,
     options.health_check_interval_s = FLAGS_channel_check_interval;
 
     if (Socket::Create(options, &sock_id) != 0) {

Review Comment:
   Right —`get_resource()` returning before`OnCreated` means no callback runs, 
so`sub_chan` (and`chan` under`OWNS_CHANNEL` ) would leak. Restored caller-owned 
cleanup in`AddChannel` : when`Socket::Create` fails we now delete the 
wrapper`sub_chan` ourselves, and`sub_channel` iff ownership is`OWNS_CHANNEL` . 
This is sound because, per the socket change,`BeforeRecycle` never runs on any 
create-failure path, so there is no double-free.`PartitionChannel` uses the 
default`OWNS_CHANNEL` and relies on schan for cleanup (its redundant`delete 
pchan` on failure was already removed), so it has a correct fallback via schan.



##########
src/brpc/socket.cpp:
##########
@@ -722,7 +722,10 @@ int Socket::Create(const SocketOptions& options, SocketId* 
id) {
 int Socket::OnCreated(const SocketOptions& options) {
     if (_io_event.Init((void*)id()) != 0) {
         LOG(ERROR) << "Fail to init IOEvent";
-        SetFailed(ENOMEM, "%s", "Fail to init IOEvent");
+        // VersionedRefWithId::Create() performs the failure transition after
+        // OnCreated() returns an error; attach the user before returning so
+        // recycling invokes SocketUser::BeforeRecycle.
+        _user = options.user;

Review Comment:
   Both valid, thanks. Fixed by no longer attaching`_user` on the failure path 
at all:`_user = options.user` is moved to the very end of`OnCreated` , so it is 
set only on the fully-constructed success path. No`OnCreated` failure (IOEvent 
init,`bthread_id_create` ,`id_wait_list` init, or`get_resource` failing 
before`OnCreated` runs) attaches the user, so recycling never 
invokes`SocketUser::BeforeRecycle` on a failed create. This removes 
the`EpollOutRequest` double-delete (`Connect` 's`delete req` becomes the sole 
cleanup) and the re-entrant`_map_mutex` acquisition in`Acceptor::StartAccept` — 
and incidentally the same latent hazards on the`bthread_id` /`id_wait_list` 
failure sub-paths.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to