@@ -14,55 +14,14 @@ When a request receives a 401 (Unauthorized) response, the SDK automatically:
14144 . ** Retries the request** with the new token
15155 . ** Tracks attempts per endpoint** to handle persistent failures gracefully
1616
17- ### Race Condition Protection (Async Client)
1817
19- The async client includes built-in protection against race conditions when multiple concurrent requests receive 401 errors:
20-
21- - Uses an async lock (` asyncio.Lock ` ) to synchronize token refresh operations
22- - Checks if another coroutine already refreshed the token before performing a refresh
23- - Validates token freshness using ` token_update_time ` and ` auth_token_ttl_sec `
24- - Ensures only one token refresh occurs even with multiple simultaneous 401 responses
25-
26- ### Synchronous vs Asynchronous Behavior
27-
28- ** Async Client (` conductor.asyncio_client ` )** :
29- - Uses ` asyncio.sleep() ` for non-blocking delays
30- - Includes race condition protection with ` asyncio.Lock `
31- - Ideal for high-concurrency scenarios and modern async applications
32-
33- ** Sync Client (` conductor.client ` )** :
18+ ** Client (` conductor.client ` )** :
3419- Uses ` time.sleep() ` for delays (blocks the current thread)
3520- Simpler implementation without lock-based synchronization
3621- Suitable for traditional synchronous applications and scripts
3722
3823## Configuration
3924
40- Both sync and async clients share the same configuration parameters.
41-
42- ### Async Client Configuration
43-
44- For the async client (` conductor.asyncio_client ` ), configure the retry policy during initialization:
45-
46- ``` python
47- from conductor.asyncio_client.configuration import Configuration
48-
49- configuration = Configuration(
50- server_url = " https://your-conductor-server.com/api" ,
51- auth_key = " your_key_id" ,
52- auth_secret = " your_key_secret" ,
53- # 401 retry configuration
54- auth_401_max_attempts = 5 , # Maximum retry attempts per endpoint
55- auth_401_base_delay_ms = 1000.0 , # Base delay in milliseconds
56- auth_401_max_delay_ms = 60000.0 , # Maximum delay cap in milliseconds
57- auth_401_jitter_percent = 0.2 , # Random jitter (20%)
58- auth_401_stop_behavior = " stop_worker" # Behavior after max attempts
59- )
60- ```
61-
62- ### Sync Client Configuration
63-
64- For the sync client (` conductor.client ` ), configuration is identical:
65-
6625``` python
6726from conductor.client.configuration.configuration import Configuration
6827
@@ -130,39 +89,6 @@ With default configuration (`base_delay_ms=1000`, `jitter_percent=0.2`, `max_del
13089
13190## Usage Examples
13291
133- ### Basic Usage with Async Client
134-
135- ``` python
136- import asyncio
137- from conductor.asyncio_client.configuration import Configuration
138- from conductor.asyncio_client.workflow_client import WorkflowClient
139-
140- async def main ():
141- # Configuration with custom retry policy
142- config = Configuration(
143- server_url = " https://your-server.com/api" ,
144- auth_key = " your_key" ,
145- auth_secret = " your_secret" ,
146- auth_401_max_attempts = 3 ,
147- auth_401_base_delay_ms = 500.0
148- )
149-
150- workflow_client = WorkflowClient(config)
151-
152- # The retry policy is automatically applied to all API calls
153- workflow = await workflow_client.start_workflow(
154- name = " my_workflow" ,
155- version = 1 ,
156- input = {" key" : " value" }
157- )
158-
159- print (f " Started workflow: { workflow.workflow_id} " )
160-
161- asyncio.run(main())
162- ```
163-
164- ### Basic Usage with Sync Client
165-
16692``` python
16793from conductor.client.configuration.configuration import Configuration
16894from conductor.client.workflow_client import WorkflowClient
@@ -188,66 +114,10 @@ workflow_id = workflow_client.start_workflow(
188114print (f " Started workflow: { workflow_id} " )
189115```
190116
191- ### Handling Multiple Concurrent Requests (Async Client)
192-
193- The async client handles concurrent requests efficiently with race condition protection:
194-
195- ``` python
196- import asyncio
197- from conductor.asyncio_client.configuration import Configuration
198- from conductor.asyncio_client.workflow_client import WorkflowClient
199-
200- async def start_multiple_workflows ():
201- config = Configuration(
202- server_url = " https://your-server.com/api" ,
203- auth_key = " your_key" ,
204- auth_secret = " your_secret"
205- )
206-
207- workflow_client = WorkflowClient(config)
208-
209- # Start multiple workflows concurrently
210- # If all receive 401, only one token refresh will occur
211- tasks = [
212- workflow_client.start_workflow(name = " workflow1" , version = 1 , input = {}),
213- workflow_client.start_workflow(name = " workflow2" , version = 1 , input = {}),
214- workflow_client.start_workflow(name = " workflow3" , version = 1 , input = {})
215- ]
216-
217- results = await asyncio.gather(* tasks)
218- return results
219-
220- asyncio.run(start_multiple_workflows())
221- ```
222-
223117### Working with Task Workers
224118
225119Both sync and async workers benefit from the retry policy:
226120
227- ** Async Worker:**
228- ``` python
229- from conductor.asyncio_client.configuration import Configuration
230- from conductor.asyncio_client.worker.worker import Worker
231- from conductor.asyncio_client.worker.worker_task import WorkerTask
232-
233- config = Configuration(
234- server_url = " https://your-server.com/api" ,
235- auth_key = " your_key" ,
236- auth_secret = " your_secret" ,
237- auth_401_max_attempts = 8 , # More attempts for long-running workers
238- auth_401_base_delay_ms = 1000.0
239- )
240-
241- @WorkerTask (task_definition_name = " example_task" , worker_id = " worker1" , domain = " test" )
242- async def example_task (task ):
243- return {" status" : " completed" }
244-
245- # Workers automatically benefit from retry policy
246- worker = Worker(config)
247- worker.start_polling()
248- ```
249-
250- ** Sync Worker:**
251121``` python
252122from conductor.client.configuration.configuration import Configuration
253123from conductor.client.worker.worker import Worker
@@ -292,7 +162,7 @@ For development environments where you want faster feedback (works for both sync
292162
293163``` python
294164# Async client
295- from conductor.asyncio_client .configuration import Configuration
165+ from conductor.client .configuration import Configuration
296166# OR Sync client
297167# from conductor.client.configuration.configuration import Configuration
298168
@@ -413,33 +283,16 @@ When `auth_401_max_attempts` is reached:
4132833 . Verify network connectivity to auth service
4142844 . Check server-side auth service logs
415285
416- ### Issue: Concurrent Requests Causing Multiple Token Refreshes (Async Client)
417-
418- ** Symptom** : Multiple token refresh operations for simultaneous requests (should not happen with race condition protection in async client)
419-
420- ** Solutions** :
421- 1 . Verify you're using the SDK version with race condition fixes (async client only)
422- 2 . Check if you're sharing the same ` Configuration ` instance across requests
423- 3 . Ensure ` asyncio.Lock ` is working correctly in your environment
424- 4 . Note: Sync client doesn't have this protection as it's not designed for concurrent requests
425-
426286## Technical Details
427287
428288### Implementation
429289
430290The retry policy is implemented in:
431- - ** Async Client** : ` src/conductor/asyncio_client/adapters/api_client_adapter.py `
432- - ** Sync Client** : ` src/conductor/client/adapters/api_client_adapter.py `
291+ - ** Client** : ` src/conductor/client/adapters/api_client_adapter.py `
433292- ** Shared Policy** : ` src/conductor/client/exceptions/auth_401_policy.py ` - Policy configuration and backoff calculation
434293
435294### Thread Safety and Concurrency
436295
437- ** Async Client:**
438- - Uses ` asyncio.Lock ` for token refresh synchronization
439- - Safe for concurrent coroutines
440- - Tokens are checked and refreshed atomically
441- - Race condition protection prevents duplicate token refreshes
442-
443296** Sync Client:**
444297- Designed for single-threaded sequential execution
445298- No explicit locking (not needed for sequential requests)
@@ -454,57 +307,11 @@ The retry policy is implemented in:
454307- Jitter prevents synchronized retry storms
455308- Per-endpoint attempt tracking allows independent retry logic
456309
457- ** Async Client Specific:**
458- - Non-blocking delays don't prevent other operations from executing
459- - Ideal for high-concurrency scenarios (workers polling multiple tasks)
460- - Lower overall latency in concurrent scenarios
461-
462- ** Sync Client Specific:**
310+ ** Client Specific:**
463311- Blocking delays pause the current thread
464312- Simpler to reason about (sequential execution)
465313- More suitable for scripts and batch processing
466314
467- ## Choosing Between Sync and Async
468-
469- Use the ** Async Client** when:
470- - Building high-concurrency applications (web servers, API gateways)
471- - Running workers that poll multiple tasks simultaneously
472- - Need non-blocking I/O operations
473- - Want maximum throughput with concurrent requests
474- - Working with modern async frameworks (FastAPI, aiohttp, etc.)
475-
476- Use the ** Sync Client** when:
477- - Writing simple scripts or batch jobs
478- - Working with traditional synchronous code
479- - Don't need concurrent request handling
480- - Prefer simpler, more straightforward code
481- - Integrating with existing sync-only libraries
482-
483- Both clients provide the same retry policy functionality with identical configuration options.
484-
485- ## Migration Between Sync and Async
486-
487- The configuration is compatible between both clients, making migration easier:
488-
489- ``` python
490- # Shared configuration
491- config_params = {
492- " server_url" : " https://your-server.com/api" ,
493- " auth_key" : " your_key" ,
494- " auth_secret" : " your_secret" ,
495- " auth_401_max_attempts" : 5 ,
496- " auth_401_base_delay_ms" : 1000.0 ,
497- }
498-
499- # Use with async client
500- from conductor.asyncio_client.configuration import Configuration as AsyncConfig
501- async_config = AsyncConfig(** config_params)
502-
503- # Or with sync client
504- from conductor.client.configuration.configuration import Configuration as SyncConfig
505- sync_config = SyncConfig(** config_params)
506- ```
507-
508315## Related Documentation
509316
510317- [ Authorization & Access Control] ( README.md )
0 commit comments