@@ -558,87 +558,95 @@ int network_socket_close(Timeout *t, SObj mallocCapability, SObj sealedSocket)
558558 }
559559 return with_sealed_socket (
560560 [=](SealedSocket *socket) {
561- // Don't use a lock guard here, we don't want to release the lock if
562- // it's been deallocated. This must be released on all return paths
563- // except the one for success.
564- if (!socket->socketLock .try_lock (t))
561+ if (LockGuard g{socket->socketLock , t})
565562 {
566- return -ETIMEDOUT;
567- }
568- // Since we free the socket and the token at the end after
569- // terminating the socket, ensure that the frees won't fail
570- if (heap_can_free (mallocCapability, socket->socket ) != 0 ||
571- token_obj_can_destroy (
572- mallocCapability, socket_key (), sealedSocket) != 0 )
573- {
574- return -EINVAL;
575- }
576- bool isTCP = socket->socket ->ucProtocol == FREERTOS_IPPROTO_TCP;
577- // Shut down the socket before closing the firewall. Don't
578- // bother with the return value: `FreeRTOS_shutdown` fails
579- // mainly if the TCP connection is dead, which is likely to
580- // happen in practice and has no impact for us.
581- FreeRTOS_shutdown (socket->socket , FREERTOS_SHUT_RDWR);
582- auto localPort = ntohs (socket->socket ->usLocalPort );
583- if (socket->socket ->bits .bIsIPv6 )
584- {
585- if (isTCP)
563+ // Since we free the socket and the token at the end after
564+ // terminating the socket, ensure that the frees won't fail
565+ if (heap_can_free (mallocCapability, socket->socket ) != 0 ||
566+ token_obj_can_destroy (
567+ mallocCapability, socket_key (), sealedSocket) != 0 )
586568 {
587- firewall_remove_tcpipv6_endpoint (localPort);
569+ Debug::log (" Unable to free socket or token." );
570+ // The main reason why this would fail is because we
571+ // were called with the wrong malloc capability. We
572+ // want to leave a chance to the caller to call us
573+ // again with the right capability.
574+ return -EINVAL;
575+ }
576+ bool isTCP = socket->socket ->ucProtocol == FREERTOS_IPPROTO_TCP;
577+ // Shut down the socket before closing the firewall. Don't
578+ // bother with the return value: `FreeRTOS_shutdown` fails
579+ // mainly if the TCP connection is dead, which is likely to
580+ // happen in practice and has no impact for us.
581+ FreeRTOS_shutdown (socket->socket , FREERTOS_SHUT_RDWR);
582+ auto localPort = ntohs (socket->socket ->usLocalPort );
583+ if (socket->socket ->bits .bIsIPv6 )
584+ {
585+ if (isTCP)
586+ {
587+ firewall_remove_tcpipv6_endpoint (localPort);
588+ }
589+ else
590+ {
591+ firewall_remove_udpipv6_local_endpoint (localPort);
592+ }
588593 }
589594 else
590595 {
591- firewall_remove_udpipv6_local_endpoint (localPort);
596+ if (isTCP)
597+ {
598+ firewall_remove_tcpipv4_endpoint (localPort);
599+ }
600+ else
601+ {
602+ firewall_remove_udpipv4_local_endpoint (localPort);
603+ }
592604 }
593- }
594- else
595- {
596- if (isTCP)
605+ // Close the socket. Another thread will actually clean up the
606+ // memory. This returns 1 on success.
607+ int ret = 0 ;
608+ auto closeStatus = close_socket_retry (t, socket->socket );
609+ if (closeStatus == 0 )
597610 {
598- firewall_remove_tcpipv4_endpoint (localPort);
611+ // The only reason why this would fail is internal
612+ // corruption (did someone already close the
613+ // socket?). Nothing can be done by anyone at this
614+ // stage. Don't return because we would leak
615+ // everything.
616+ ret = -ENOTRECOVERABLE;
599617 }
600- else
618+ else if (closeStatus != 1 )
601619 {
602- firewall_remove_udpipv4_local_endpoint (localPort);
620+ // The close couldn't be delivered to the IP
621+ // task. With some luck, the socket can be
622+ // freed next time we try.
623+ return -ETIMEDOUT;
603624 }
625+ g.release ();
626+ socket->socketLock .upgrade_for_destruction ();
627+ // Drop the caller's claim on the socket.
628+ if (heap_free (mallocCapability, socket->socket ) != 0 )
629+ {
630+ // This is not supposed to happen, since we did a
631+ // `heap_can_free` earlier (unless we did not have
632+ // enough stack, or a concurrent free happened). If
633+ // it does, we may be leaking the socket.
634+ Debug::log (" Failed to free socket." );
635+ // Don't return yet, try to at least free the token.
636+ ret = -ENOTRECOVERABLE;
637+ }
638+ if (token_obj_destroy (
639+ mallocCapability, socket_key (), sealedSocket) != 0 )
640+ {
641+ // This is not supposed to happen, since we did a
642+ // `token_obj_can_destroy` earlier (see comment
643+ // above). If it does, we're leaking the token.
644+ Debug::log (" Failed to free token." );
645+ ret = -ENOTRECOVERABLE;
646+ }
647+ return ret;
604648 }
605- // Close the socket. Another thread will actually clean up the
606- // memory.
607- auto closeStatus = close_socket_retry (t, socket->socket );
608- if (closeStatus == 0 )
609- {
610- return -EINVAL;
611- }
612- if (closeStatus != 1 )
613- {
614- // With some lock, the socket can be freed next time
615- // we try.
616- return -ETIMEDOUT;
617- }
618- socket->socketLock .upgrade_for_destruction ();
619- // Drop the caller's claim on the socket.
620- int ret = 0 ;
621- if (heap_free (mallocCapability, socket->socket ) != 0 )
622- {
623- // This is not supposed to happen, since we did a
624- // heap_can_free earlier. If it does, we're leaking
625- // the socket.
626- Debug::log (" Failed to free socket." );
627- // Release the lock so that we don't leak it.
628- socket->socketLock .unlock ();
629- // Don't return now, try to at least free the token.
630- ret = -EINVAL;
631- }
632- if (token_obj_destroy (mallocCapability, socket_key (), sealedSocket) !=
633- 0 )
634- {
635- // This is not supposed to happen, since we did a
636- // token_obj_can_destroy earlier. If it does, we're
637- // leaking the token.
638- Debug::log (" Failed to free token." );
639- ret = -EINVAL;
640- }
641- return ret;
649+ return -ETIMEDOUT;
642650 },
643651 sealedSocket);
644652}
0 commit comments