|
13 | 13 | #import "FBLogger.h" |
14 | 14 | #import "FBResponsePayload.h" |
15 | 15 | #import "FBTCPSocket.h" |
| 16 | +#import "FBSession.h" |
16 | 17 |
|
17 | 18 | static NSData *FBCRLFCRLFData(void) |
18 | 19 | { |
@@ -131,10 +132,6 @@ @interface FBHTTPServer () <FBTCPSocketDelegate> |
131 | 132 | // standalone or not (except DELETE /session itself - see -dispatchMethod:). See |
132 | 133 | // -abandonPendingRequestsForSessionID:. Guarded by @synchronized(self.pendingSessionRequests). |
133 | 134 | @property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableSet<FBPendingRequest *> *> *pendingSessionRequests; |
134 | | -// Already-abandoned sessions mapped to the response they were abandoned with, so a request parsed |
135 | | -// after that point is answered at once instead of queueing for a session that is gone. Kept for |
136 | | -// the server's lifetime; ids are UUIDs. Guarded by @synchronized(self.pendingSessionRequests). |
137 | | -@property (nonatomic, strong) NSMutableDictionary<NSString *, RouteResponse *> *abandonedSessionResponses; |
138 | 135 | // When each connection started waiting for its current request. The reaper closes connections |
139 | 136 | // whose entry outlives FBIncompleteRequestTimeout; idle keep-alive connections have no entry and |
140 | 137 | // are exempt. Guarded by @synchronized(self.connectionBuffers). |
@@ -163,7 +160,6 @@ - (instancetype)init |
163 | 160 | _connectionsAwaitingResponse = [NSMutableSet set]; |
164 | 161 | _standaloneWaiters = [NSMutableDictionary dictionary]; |
165 | 162 | _pendingSessionRequests = [NSMutableDictionary dictionary]; |
166 | | - _abandonedSessionResponses = [NSMutableDictionary dictionary]; |
167 | 163 | _incompleteRequestStarts = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory) |
168 | 164 | valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory]; |
169 | 165 | } |
@@ -739,9 +735,15 @@ - (void)dispatchMethod:(NSString *)method pathAndQuery:(NSString *)pathAndQuery |
739 | 735 | - (nullable RouteResponse *)trackPendingRequest:(FBPendingRequest *)pendingRequest forSessionID:(NSString *)sessionID |
740 | 736 | { |
741 | 737 | @synchronized (self.pendingSessionRequests) { |
742 | | - RouteResponse *abandonedResponse = self.abandonedSessionResponses[sessionID]; |
743 | | - if (nil != abandonedResponse) { |
744 | | - return abandonedResponse; |
| 738 | + // Checking the current session under this lock closes the admission/teardown |
| 739 | + // race without retaining a tombstone and a full response for every past UUID. |
| 740 | + // -kill clears the active session before posting its abandonment notification. |
| 741 | + if (nil == [FBSession sessionWithIdentifier:sessionID]) { |
| 742 | + RouteResponse *response = [RouteResponse new]; |
| 743 | + [FBResponseWithStatus([FBCommandStatus noSuchDriverErrorWithMessage:@"Session does not exist" |
| 744 | + traceback:nil]) dispatchWithResponse:response]; |
| 745 | + [self applyDefaultHeadersToResponse:response]; |
| 746 | + return response; |
745 | 747 | } |
746 | 748 | NSMutableSet<FBPendingRequest *> *pendingRequests = self.pendingSessionRequests[sessionID]; |
747 | 749 | if (nil == pendingRequests) { |
@@ -776,8 +778,6 @@ - (void)abandonPendingRequestsForSessionID:(NSString *)sessionID withResponse:(R |
776 | 778 | @synchronized (self.pendingSessionRequests) { |
777 | 779 | pendingRequests = [self.pendingSessionRequests[sessionID] copy]; |
778 | 780 | [self.pendingSessionRequests removeObjectForKey:sessionID]; |
779 | | - // Recorded before the lock is dropped, so requests admitted from here on are rejected. |
780 | | - self.abandonedSessionResponses[sessionID] = response; |
781 | 781 | } |
782 | 782 | for (FBPendingRequest *pendingRequest in pendingRequests) { |
783 | 783 | [self writeResponse:response toClient:pendingRequest.client]; |
|
0 commit comments