appium-webdriveragent 16.13.2 → 16.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [16.13.3](https://github.com/appium/WebDriverAgent/compare/v16.13.2...v16.13.3) (2026-09-30)
2
+
3
+ ### Bug Fixes
4
+
5
+ * reject stale sessions without retaining past responses ([#1282](https://github.com/appium/WebDriverAgent/issues/1282)) ([f245ffb](https://github.com/appium/WebDriverAgent/commit/f245ffb4e6906143384410cf312abc5bcedf5486))
6
+
1
7
  ## [16.13.2](https://github.com/appium/WebDriverAgent/compare/v16.13.1...v16.13.2) (2026-09-30)
2
8
 
3
9
  ### Bug Fixes
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.13.2</string>
18
+ <string>16.13.3</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.13.2</string>
22
+ <string>16.13.3</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -13,6 +13,7 @@
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 @@ static BOOL FBParseContentLength(NSString *value, NSUInteger *outLength)
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 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
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 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
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 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
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];
@@ -13,6 +13,7 @@
13
13
  #import <sys/socket.h>
14
14
 
15
15
  #import "FBHTTPServer.h"
16
+ #import "FBSession-Private.h"
16
17
 
17
18
  static atomic_int gSessionProbeHits;
18
19
 
@@ -23,10 +24,26 @@ static atomic_int gSessionProbeHits;
23
24
 
24
25
  @implementation FBHTTPServerSessionTests
25
26
 
27
+ - (void)testAbandonmentDoesNotRetainResponsesAfterPendingRequestsDrain
28
+ {
29
+ __weak RouteResponse *weakResponse;
30
+ @autoreleasepool {
31
+ RouteResponse *response = [RouteResponse new];
32
+ [response respondWithString:@"deleted"];
33
+ weakResponse = response;
34
+ for (NSUInteger index = 0; index < 1000; index++) {
35
+ [self.server abandonPendingRequestsForSessionID:[NSString stringWithFormat:@"old-%lu", (unsigned long)index]
36
+ withResponse:response];
37
+ }
38
+ }
39
+ XCTAssertNil(weakResponse);
40
+ }
41
+
26
42
  - (void)setUp
27
43
  {
28
44
  [super setUp];
29
45
  atomic_store(&gSessionProbeHits, 0);
46
+ [FBSession initWithApplication:nil].identifier = @"live-session";
30
47
  self.server = [FBHTTPServer new];
31
48
  [self.server get:@"/session/:sessionID/probe" withBlock:^(RouteRequest *request, RouteResponse *response) {
32
49
  atomic_fetch_add(&gSessionProbeHits, 1);
@@ -42,6 +59,7 @@ static atomic_int gSessionProbeHits;
42
59
  {
43
60
  [self.server stop:NO];
44
61
  self.server = nil;
62
+ [FBSession.activeSession kill];
45
63
  [super tearDown];
46
64
  }
47
65
 
@@ -93,14 +111,14 @@ static atomic_int gSessionProbeHits;
93
111
 
94
112
  NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
95
113
  timeout:5.0];
96
- XCTAssertTrue([response containsString:@"session-was-deleted"], @"%@", response);
114
+ XCTAssertTrue([response containsString:@"invalid session id"], @"%@", response);
97
115
  XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
98
116
  }
99
117
 
100
118
  - (void)testAbandonedSessionIsRememberedAfterManyLaterAbandonments
101
119
  {
102
- // Abandoned ids are kept for the server's lifetime; evicting them would let a stale request
103
- // queue on a possibly wedged route queue again, which is the hang this rejection prevents.
120
+ // A stale UUID must still be rejected after arbitrarily many other sessions,
121
+ // without needing to remember every deleted UUID.
104
122
  RouteResponse *abandonedResponse = [RouteResponse new];
105
123
  [abandonedResponse respondWithString:@"session-was-deleted"];
106
124
  [self.server abandonPendingRequestsForSessionID:@"dead-session" withResponse:abandonedResponse];
@@ -113,7 +131,7 @@ static atomic_int gSessionProbeHits;
113
131
 
114
132
  NSString *response = [self responseForRawPayload:(NSData * _Nonnull)[@"GET /session/dead-session/probe HTTP/1.1\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]
115
133
  timeout:5.0];
116
- XCTAssertTrue([response containsString:@"session-was-deleted"], @"%@", response);
134
+ XCTAssertTrue([response containsString:@"invalid session id"], @"%@", response);
117
135
  XCTAssertEqual(atomic_load(&gSessionProbeHits), 0, @"the route must not run for a deleted session");
118
136
  }
119
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.13.2",
3
+ "version": "16.13.3",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",