3
3
import os
4
4
import threading
5
5
import time
6
- from concurrent .futures import ThreadPoolExecutor
7
6
from datetime import datetime
8
7
from decimal import Decimal
9
- from unittest .mock import MagicMock , Mock , patch
8
+ from unittest .mock import MagicMock , patch
10
9
11
10
import pytest
12
11
from starlette .testclient import TestClient
@@ -235,25 +234,19 @@ def handler(payload):
235
234
236
235
237
236
class TestConcurrentInvocations :
238
- """Test concurrent invocation handling with thread pool and semaphore ."""
237
+ """Test concurrent invocation handling simplified without limits ."""
239
238
240
- def test_thread_pool_initialization (self ):
241
- """Test ThreadPoolExecutor and Semaphore are properly initialized ."""
239
+ def test_simplified_initialization (self ):
240
+ """Test that app initializes without thread pool and semaphore ."""
242
241
app = BedrockAgentCoreApp ()
243
242
244
- # Check ThreadPoolExecutor is initialized with correct settings
245
- assert hasattr (app , "_invocation_executor" )
246
- assert isinstance (app ._invocation_executor , ThreadPoolExecutor )
247
- assert app ._invocation_executor ._max_workers == 2
248
-
249
- # Check Semaphore is initialized with correct limit
250
- assert hasattr (app , "_invocation_semaphore" )
251
- assert isinstance (app ._invocation_semaphore , asyncio .Semaphore )
252
- assert app ._invocation_semaphore ._value == 2
243
+ # Check ThreadPoolExecutor and Semaphore are NOT initialized
244
+ assert not hasattr (app , "_invocation_executor" )
245
+ assert not hasattr (app , "_invocation_semaphore" )
253
246
254
247
@pytest .mark .asyncio
255
- async def test_concurrent_invocations_within_limit (self ):
256
- """Test that 2 concurrent requests work fine ."""
248
+ async def test_concurrent_invocations_unlimited (self ):
249
+ """Test that multiple concurrent requests work without limits ."""
257
250
app = BedrockAgentCoreApp ()
258
251
259
252
# Create a slow sync handler
@@ -262,66 +255,26 @@ def handler(payload):
262
255
time .sleep (0.1 ) # Simulate work
263
256
return {"id" : payload ["id" ]}
264
257
265
- # Mock the executor to track calls
266
- original_executor = app ._invocation_executor
267
- mock_executor = Mock (wraps = original_executor )
268
- app ._invocation_executor = mock_executor
269
-
270
258
# Create request context
271
259
from bedrock_agentcore .runtime .context import RequestContext
272
260
273
261
context = RequestContext (session_id = None )
274
262
275
- # Start 2 concurrent invocations
263
+ # Start 3+ concurrent invocations (no limit)
276
264
task1 = asyncio .create_task (app ._invoke_handler (handler , context , False , {"id" : 1 }))
277
265
task2 = asyncio .create_task (app ._invoke_handler (handler , context , False , {"id" : 2 }))
266
+ task3 = asyncio .create_task (app ._invoke_handler (handler , context , False , {"id" : 3 }))
278
267
279
- # Both should complete successfully
268
+ # All should complete successfully
280
269
result1 = await task1
281
270
result2 = await task2
271
+ result3 = await task3
282
272
283
273
assert result1 == {"id" : 1 }
284
274
assert result2 == {"id" : 2 }
275
+ assert result3 == {"id" : 3 }
285
276
286
- # Verify executor was used for sync handlers
287
- assert mock_executor .submit .call_count >= 2
288
-
289
- @pytest .mark .asyncio
290
- async def test_concurrent_invocations_exceed_limit (self ):
291
- """Test that 3rd concurrent request gets 503 response."""
292
- app = BedrockAgentCoreApp ()
293
-
294
- # Create a slow handler
295
- @app .entrypoint
296
- def handler (payload ):
297
- time .sleep (0.5 ) # Simulate long work
298
- return {"id" : payload ["id" ]}
299
-
300
- # Create request context
301
- from bedrock_agentcore .runtime .context import RequestContext
302
-
303
- context = RequestContext (session_id = None )
304
-
305
- # Start 2 invocations to fill the semaphore
306
- task1 = asyncio .create_task (app ._invoke_handler (handler , context , False , {"id" : 1 }))
307
- task2 = asyncio .create_task (app ._invoke_handler (handler , context , False , {"id" : 2 }))
308
-
309
- # Wait a bit to ensure they've acquired the semaphore
310
- await asyncio .sleep (0.1 )
311
-
312
- # Third invocation should get 503
313
- result3 = await app ._invoke_handler (handler , context , False , {"id" : 3 })
314
-
315
- # Verify it's a JSONResponse with 503 status
316
- from starlette .responses import JSONResponse
317
-
318
- assert isinstance (result3 , JSONResponse )
319
- assert result3 .status_code == 503
320
- assert result3 .body == b'{"error":"Server busy - maximum concurrent requests reached"}'
321
-
322
- # Clean up the running tasks
323
- await task1
324
- await task2
277
+ # Removed: No more 503 responses since we removed concurrency limits
325
278
326
279
@pytest .mark .asyncio
327
280
async def test_async_handler_runs_in_event_loop (self ):
@@ -338,10 +291,6 @@ async def handler(payload):
338
291
await asyncio .sleep (0.01 )
339
292
return {"async" : True }
340
293
341
- # Mock the executor to ensure it's NOT used for async handlers
342
- mock_executor = Mock ()
343
- app ._invocation_executor = mock_executor
344
-
345
294
# Create request context
346
295
from bedrock_agentcore .runtime .context import RequestContext
347
296
@@ -353,12 +302,11 @@ async def handler(payload):
353
302
assert result == {"async" : True }
354
303
# Async handler should run in main thread
355
304
assert handler_thread_id == threading .current_thread ().ident
356
- # Executor should NOT be used for async handlers
357
- mock_executor .submit .assert_not_called ()
305
+ # No executor needed for async handlers
358
306
359
307
@pytest .mark .asyncio
360
308
async def test_sync_handler_runs_in_thread_pool (self ):
361
- """Test sync handlers run in thread pool ."""
309
+ """Test sync handlers run in default executor, not main event loop ."""
362
310
app = BedrockAgentCoreApp ()
363
311
364
312
# Track which thread the handler runs in
@@ -379,36 +327,14 @@ def handler(payload):
379
327
result = await app ._invoke_handler (handler , context , False , {})
380
328
381
329
assert result == {"sync" : True }
382
- # Sync handler should NOT run in main thread
330
+ # Sync handler should NOT run in main thread (uses default executor)
383
331
assert handler_thread_id != threading .current_thread ().ident
384
332
385
- @pytest .mark .asyncio
386
- async def test_semaphore_release_after_completion (self ):
387
- """Test semaphore is properly released after request completion."""
388
- app = BedrockAgentCoreApp ()
389
-
390
- @app .entrypoint
391
- def handler (payload ):
392
- return {"result" : "ok" }
393
-
394
- # Create request context
395
- from bedrock_agentcore .runtime .context import RequestContext
396
-
397
- context = RequestContext (session_id = None )
398
-
399
- # Check initial semaphore value
400
- assert app ._invocation_semaphore ._value == 2
401
-
402
- # Make a request
403
- result = await app ._invoke_handler (handler , context , False , {})
404
- assert result == {"result" : "ok" }
405
-
406
- # Semaphore should be released
407
- assert app ._invocation_semaphore ._value == 2
333
+ # Removed: No semaphore to test
408
334
409
335
@pytest .mark .asyncio
410
- async def test_handler_exception_releases_semaphore (self ):
411
- """Test semaphore is released even when handler fails ."""
336
+ async def test_handler_exception_propagates (self ):
337
+ """Test handler exceptions are properly propagated ."""
412
338
app = BedrockAgentCoreApp ()
413
339
414
340
@app .entrypoint
@@ -420,16 +346,10 @@ def handler(payload):
420
346
421
347
context = RequestContext (session_id = None )
422
348
423
- # Check initial semaphore value
424
- assert app ._invocation_semaphore ._value == 2
425
-
426
- # Make a request that will fail
349
+ # Exception should propagate
427
350
with pytest .raises (ValueError , match = "Test error" ):
428
351
await app ._invoke_handler (handler , context , False , {})
429
352
430
- # Semaphore should still be released
431
- assert app ._invocation_semaphore ._value == 2
432
-
433
353
def test_no_thread_leak_on_repeated_requests (self ):
434
354
"""Test that repeated requests don't leak threads."""
435
355
app = BedrockAgentCoreApp ()
@@ -450,46 +370,11 @@ def handler(payload):
450
370
assert response .json () == {"id" : i }
451
371
452
372
# Thread count should not have increased significantly
453
- # Allow for some variance but no leak
373
+ # Allow for some variance but no leak (uses default executor)
454
374
final_thread_count = threading .active_count ()
455
- assert final_thread_count <= initial_thread_count + 2 # Thread pool has max 2 threads
456
-
457
- @pytest .mark .asyncio
458
- async def test_server_busy_error_format (self ):
459
- """Test 503 response has correct error message format."""
460
- app = BedrockAgentCoreApp ()
461
-
462
- # Fill the semaphore
463
- await app ._invocation_semaphore .acquire ()
464
- await app ._invocation_semaphore .acquire ()
465
-
466
- @app .entrypoint
467
- def handler (payload ):
468
- return {"ok" : True }
469
-
470
- # Create request context
471
- from bedrock_agentcore .runtime .context import RequestContext
472
-
473
- context = RequestContext (session_id = None )
474
-
475
- # Try to invoke when semaphore is full
476
- result = await app ._invoke_handler (handler , context , False , {})
477
-
478
- # Check response format
479
- from starlette .responses import JSONResponse
480
-
481
- assert isinstance (result , JSONResponse )
482
- assert result .status_code == 503
483
-
484
- # Parse the JSON body
485
- import json
486
-
487
- body = json .loads (result .body )
488
- assert body == {"error" : "Server busy - maximum concurrent requests reached" }
375
+ assert final_thread_count <= initial_thread_count + 10 # Default executor may create more threads
489
376
490
- # Release semaphore
491
- app ._invocation_semaphore .release ()
492
- app ._invocation_semaphore .release ()
377
+ # Removed: No more server busy errors
493
378
494
379
def test_ping_endpoint_remains_sync (self ):
495
380
"""Test that ping endpoint is not async."""
0 commit comments