Skip to content

Commit 9b93f7d

Browse files
committed
updates
1 parent 78100b2 commit 9b93f7d

16 files changed

Lines changed: 4138 additions & 2144 deletions

WORKER_CONFIGURATION.md

Lines changed: 181 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,80 @@ The following properties can be configured via environment variables:
2121

2222
| Property | Type | Description | Example | Decorator? |
2323
|----------|------|-------------|---------|------------|
24-
| `poll_interval` | float | Polling interval in milliseconds | `1000` | ✅ Yes |
24+
| `poll_interval_millis` | int | Polling interval in milliseconds | `1000` | ✅ Yes |
2525
| `domain` | string | Worker domain for task routing | `production` | ✅ Yes |
2626
| `worker_id` | string | Unique worker identifier | `worker-1` | ✅ Yes |
27-
| `thread_count` | int | Number of concurrent threads/coroutines | `10` | ✅ Yes |
27+
| `thread_count` | int | Max concurrent executions (threads for sync, coroutines for async) | `10` | ✅ Yes |
2828
| `register_task_def` | bool | Auto-register task definition | `true` | ✅ Yes |
2929
| `poll_timeout` | int | Poll request timeout in milliseconds | `100` | ✅ Yes |
30-
| `lease_extend_enabled` | bool | Enable automatic lease extension | `false` | ✅ Yes |
30+
| `lease_extend_enabled` | bool | ⚠️ **Not implemented** - reserved for future use | `false` | ✅ Yes |
3131
| `paused` | bool | Pause worker from polling/executing tasks | `true` |**Environment-only** |
3232

33-
**Note**: The `paused` property is intentionally **not available** in the `@worker_task` decorator. It can only be controlled via environment variables, allowing operators to pause/resume workers at runtime without code changes or redeployment.
33+
**Notes**:
34+
- The `paused` property is intentionally **not available** in the `@worker_task` decorator. It can only be controlled via environment variables, allowing operators to pause/resume workers at runtime without code changes or redeployment.
35+
- The `lease_extend_enabled` parameter is accepted but **not currently implemented**. For lease extension, use manual `TaskInProgress` returns (see below).
36+
37+
### Understanding `thread_count`
38+
39+
The `thread_count` parameter has different meanings depending on worker type (automatically detected from function signature):
40+
41+
**Sync Workers (`def`):**
42+
- Controls ThreadPoolExecutor size
43+
- Each task consumes one thread
44+
- Recommended: 1-4 for CPU-bound, 10-50 for I/O-bound
45+
46+
**Async Workers (`async def`):**
47+
- Controls max concurrent async tasks (semaphore limit)
48+
- All tasks share single event loop
49+
- Recommended: 50-200 for I/O-bound (event loop handles thousands)
50+
51+
**Example:**
52+
```python
53+
# Sync worker - thread_count = thread pool size
54+
@worker_task(task_definition_name='cpu_task', thread_count=4)
55+
def cpu_task(data: dict) -> dict:
56+
return expensive_computation(data)
57+
58+
# Async worker - thread_count = concurrency limit (not threads!)
59+
@worker_task(task_definition_name='api_task', thread_count=100)
60+
async def api_task(url: str) -> dict:
61+
async with httpx.AsyncClient() as client:
62+
return await client.get(url)
63+
# Only 1 thread, but 100 concurrent tasks!
64+
```
65+
66+
**For more details**, see [Worker Design Documentation](docs/design/WORKER_DESIGN.md).
67+
68+
### Lease Extension for Long-Running Tasks
69+
70+
**Current Implementation**: Only manual lease extension via `TaskInProgress` is supported.
71+
72+
```python
73+
from conductor.client.context.task_context import TaskInProgress, get_task_context
74+
from typing import Union
75+
76+
@worker_task(task_definition_name='long_running_task')
77+
def long_task(job_id: str) -> Union[dict, TaskInProgress]:
78+
ctx = get_task_context()
79+
poll_count = ctx.get_poll_count()
80+
81+
# Process chunk of work
82+
processed = process_chunk(job_id, poll_count)
83+
84+
if not is_complete(job_id):
85+
# More work to do - extend lease by returning TaskInProgress
86+
return TaskInProgress(
87+
callback_after_seconds=60, # Return to queue after 60s
88+
output={'progress': processed}
89+
)
90+
else:
91+
# Done - return final result
92+
return {'status': 'completed', 'result': processed}
93+
```
94+
95+
**⚠️ Note**: The `lease_extend_enabled=True` configuration parameter does **not** provide automatic lease extension. You must explicitly return `TaskInProgress` to extend the lease.
96+
97+
**For detailed patterns**, see [Long-Running Tasks & Lease Extension](docs/design/WORKER_DESIGN.md#long-running-tasks--lease-extension).
3498

3599
## Environment Variable Format
36100

@@ -52,7 +116,7 @@ from conductor.client.worker.worker_task import worker_task
52116

53117
@worker_task(
54118
task_definition_name='process_order',
55-
poll_interval=1000,
119+
poll_interval_millis=1000,
56120
domain='dev',
57121
thread_count=5
58122
)
@@ -62,30 +126,30 @@ def process_order(order_id: str) -> dict:
62126

63127
### Without Environment Variables
64128
Worker uses code-level defaults:
65-
- `poll_interval=1000`
129+
- `poll_interval_millis=1000`
66130
- `domain='dev'`
67131
- `thread_count=5`
68132

69133
### With Global Override
70134
```bash
71-
export conductor.worker.all.poll_interval=500
135+
export conductor.worker.all.poll_interval_millis=500
72136
export conductor.worker.all.domain=production
73137
```
74138

75139
Worker now uses:
76-
- `poll_interval=500` (from global env)
140+
- `poll_interval_millis=500` (from global env)
77141
- `domain='production'` (from global env)
78142
- `thread_count=5` (from code)
79143

80144
### With Worker-Specific Override
81145
```bash
82-
export conductor.worker.all.poll_interval=500
146+
export conductor.worker.all.poll_interval_millis=500
83147
export conductor.worker.all.domain=production
84148
export conductor.worker.process_order.thread_count=20
85149
```
86150

87151
Worker now uses:
88-
- `poll_interval=500` (from global env)
152+
- `poll_interval_millis=500` (from global env)
89153
- `domain='production'` (from global env)
90154
- `thread_count=20` (from worker-specific env)
91155

@@ -98,37 +162,36 @@ Override all workers to use production domain and optimized settings:
98162
```bash
99163
# Global production settings
100164
export conductor.worker.all.domain=production
101-
export conductor.worker.all.poll_interval=250
102-
export conductor.worker.all.lease_extend_enabled=true
165+
export conductor.worker.all.poll_interval_millis=250
103166

104167
# Critical worker needs more resources
105168
export conductor.worker.process_payment.thread_count=50
106-
export conductor.worker.process_payment.poll_interval=50
169+
export conductor.worker.process_payment.poll_interval_millis=50
107170
```
108171

109172
```python
110173
# Code remains unchanged
111-
@worker_task(task_definition_name='process_order', poll_interval=1000, domain='dev', thread_count=5)
174+
@worker_task(task_definition_name='process_order', poll_interval_millis=1000, domain='dev', thread_count=5)
112175
def process_order(order_id: str):
113176
...
114177

115-
@worker_task(task_definition_name='process_payment', poll_interval=1000, domain='dev', thread_count=5)
178+
@worker_task(task_definition_name='process_payment', poll_interval_millis=1000, domain='dev', thread_count=5)
116179
def process_payment(payment_id: str):
117180
...
118181
```
119182

120183
Result:
121-
- `process_order`: domain=production, poll_interval=250, thread_count=5
122-
- `process_payment`: domain=production, poll_interval=50, thread_count=50
184+
- `process_order`: domain=production, poll_interval_millis=250, thread_count=5
185+
- `process_payment`: domain=production, poll_interval_millis=50, thread_count=50
123186

124187
### Development/Debug Mode
125188

126189
Slow down polling for easier debugging:
127190

128191
```bash
129-
export conductor.worker.all.poll_interval=10000 # 10 seconds
130-
export conductor.worker.all.thread_count=1 # Single-threaded
131-
export conductor.worker.all.poll_timeout=5000 # 5 second timeout
192+
export conductor.worker.all.poll_interval_millis=10000 # 10 seconds
193+
export conductor.worker.all.thread_count=1 # Single concurrent task
194+
export conductor.worker.all.poll_timeout=5000 # 5 second timeout
132195
```
133196

134197
All workers will use these debug-friendly settings without code changes.
@@ -143,6 +206,39 @@ export conductor.worker.all.domain=staging
143206

144207
All workers use staging domain, but keep their code-defined poll intervals, thread counts, etc.
145208

209+
### High-Concurrency Async Workers
210+
211+
For async I/O-bound workers, increase concurrency significantly:
212+
213+
```bash
214+
# Global settings for async workers
215+
export conductor.worker.all.domain=production
216+
export conductor.worker.all.poll_interval_millis=100 # Lower polling delay for async
217+
218+
# Async worker - high concurrency (event loop can handle it!)
219+
export conductor.worker.fetch_api_data.thread_count=200
220+
221+
# Sync worker - keep moderate thread count
222+
export conductor.worker.process_cpu_task.thread_count=10
223+
```
224+
225+
```python
226+
# Async worker - high concurrency with single event loop
227+
@worker_task(task_definition_name='fetch_api_data')
228+
async def fetch_api_data(url: str):
229+
async with httpx.AsyncClient() as client:
230+
return await client.get(url)
231+
232+
# Sync worker - traditional thread pool
233+
@worker_task(task_definition_name='process_cpu_task')
234+
def process_cpu_task(data: dict):
235+
return expensive_computation(data)
236+
```
237+
238+
**Result**:
239+
- `fetch_api_data`: 200 concurrent async tasks in 1 thread!
240+
- `process_cpu_task`: 10 threads for CPU-bound work
241+
146242
### Pausing Workers
147243

148244
Temporarily disable workers without stopping the process:
@@ -201,7 +297,7 @@ Test new configuration on one worker before rolling out to all:
201297
```bash
202298
# Production settings for all workers
203299
export conductor.worker.all.domain=production
204-
export conductor.worker.all.poll_interval=200
300+
export conductor.worker.all.poll_interval_millis=200
205301

206302
# Canary worker uses staging domain for testing
207303
export conductor.worker.canary_worker.domain=staging
@@ -231,7 +327,7 @@ services:
231327
image: my-conductor-worker
232328
environment:
233329
- conductor.worker.all.domain=production
234-
- conductor.worker.all.poll_interval=250
330+
- conductor.worker.all.poll_interval_millis=250
235331
- conductor.worker.critical_task.thread_count=50
236332
```
237333
@@ -244,7 +340,7 @@ metadata:
244340
name: worker-config
245341
data:
246342
conductor.worker.all.domain: "production"
247-
conductor.worker.all.poll_interval: "250"
343+
conductor.worker.all.poll_interval_millis: "250"
248344
conductor.worker.critical_task.thread_count: "50"
249345
---
250346
apiVersion: v1
@@ -277,7 +373,7 @@ spec:
277373
env:
278374
- name: conductor.worker.all.domain
279375
value: "production"
280-
- name: conductor.worker.all.poll_interval
376+
- name: conductor.worker.all.poll_interval_millis
281377
value: "250"
282378
---
283379
apiVersion: apps/v1
@@ -294,7 +390,7 @@ spec:
294390
env:
295391
- name: conductor.worker.all.domain
296392
value: "staging"
297-
- name: conductor.worker.all.poll_interval
393+
- name: conductor.worker.all.poll_interval_millis
298394
value: "500"
299395
```
300396
@@ -308,19 +404,19 @@ from conductor.client.worker.worker_config import resolve_worker_config, get_wor
308404
# Resolve configuration for a worker
309405
config = resolve_worker_config(
310406
worker_name='process_order',
311-
poll_interval=1000,
407+
poll_interval_millis=1000,
312408
domain='dev',
313409
thread_count=5
314410
)
315411

316412
print(config)
317-
# {'poll_interval': 500.0, 'domain': 'production', 'thread_count': 5, ...}
413+
# {'poll_interval_millis': 500, 'domain': 'production', 'thread_count': 5, ...}
318414

319415
# Get human-readable summary
320416
summary = get_worker_config_summary('process_order', config)
321417
print(summary)
322418
# Worker 'process_order' configuration:
323-
# poll_interval: 500.0 (from conductor.worker.all.poll_interval)
419+
# poll_interval_millis: 500 (from conductor.worker.all.poll_interval_millis)
324420
# domain: production (from conductor.worker.all.domain)
325421
# thread_count: 5 (from code)
326422
```
@@ -342,11 +438,11 @@ export conductor.worker.worker3.domain=production
342438
```bash
343439
# Global settings for most workers
344440
export conductor.worker.all.thread_count=10
345-
export conductor.worker.all.poll_interval=250
441+
export conductor.worker.all.poll_interval_millis=250
346442

347443
# Exception: High-priority worker needs more resources
348444
export conductor.worker.critical_task.thread_count=50
349-
export conductor.worker.critical_task.poll_interval=50
445+
export conductor.worker.critical_task.poll_interval_millis=50
350446
```
351447

352448
### 3. Keep Code Defaults Sensible
@@ -355,10 +451,9 @@ Use sensible defaults in code so workers work without environment variables:
355451
```python
356452
@worker_task(
357453
task_definition_name='process_order',
358-
poll_interval=1000, # Reasonable default
359-
domain='dev', # Safe default domain
360-
thread_count=5, # Moderate concurrency
361-
lease_extend_enabled=False # Default: disabled
454+
poll_interval_millis=1000, # Reasonable default (1 second)
455+
domain='dev', # Safe default domain
456+
thread_count=5 # Moderate concurrency
362457
)
363458
def process_order(order_id: str):
364459
...
@@ -374,12 +469,12 @@ Maintain a README or wiki documenting required environment variables for each de
374469
- `conductor.worker.all.domain=production`
375470

376471
## Optional (Recommended)
377-
- `conductor.worker.all.poll_interval=250`
378-
- `conductor.worker.all.lease_extend_enabled=true`
472+
- `conductor.worker.all.poll_interval_millis=250`
473+
- `conductor.worker.all.thread_count=20`
379474

380475
## Worker-Specific Overrides
381476
- `conductor.worker.critical_task.thread_count=50`
382-
- `conductor.worker.critical_task.poll_interval=50`
477+
- `conductor.worker.critical_task.poll_interval_millis=50`
383478
```
384479

385480
### 5. Use Infrastructure as Code
@@ -397,8 +492,12 @@ resource "kubernetes_deployment" "worker" {
397492
value = var.environment_name
398493
}
399494
env {
400-
name = "conductor.worker.all.poll_interval"
401-
value = var.worker_poll_interval
495+
name = "conductor.worker.all.poll_interval_millis"
496+
value = var.worker_poll_interval_millis
497+
}
498+
env {
499+
name = "conductor.worker.all.thread_count"
500+
value = var.worker_thread_count
402501
}
403502
}
404503
}
@@ -467,5 +566,47 @@ The hierarchical worker configuration system provides flexibility to:
467566
- **Override at runtime**: No code changes needed for environment-specific settings
468567
- **Fine-tune per worker**: Optimize critical workers without affecting others
469568
- **Simplify management**: Use global settings for common configurations
569+
- **Pause/resume at runtime**: Control worker execution without redeployment
570+
571+
**Configuration priority**: Worker-specific > Global > Code defaults
572+
573+
### Key Configuration Patterns
574+
575+
**Sync Workers (CPU-bound):**
576+
```bash
577+
export conductor.worker.cpu_task.thread_count=4 # Thread pool size
578+
export conductor.worker.cpu_task.poll_interval_millis=500 # Moderate polling
579+
```
580+
581+
**Async Workers (I/O-bound):**
582+
```bash
583+
export conductor.worker.api_task.thread_count=100 # High concurrency
584+
export conductor.worker.api_task.poll_interval_millis=100 # Fast polling
585+
```
586+
587+
**Long-Running Tasks:**
588+
```bash
589+
# Note: Use TaskInProgress for lease extension (lease_extend_enabled not implemented)
590+
export conductor.worker.ml_training.thread_count=2 # Limit concurrent long tasks
591+
export conductor.worker.ml_training.poll_interval_millis=500
592+
```
593+
594+
---
595+
596+
## Additional Resources
597+
598+
- **[Worker Design Documentation](docs/design/WORKER_DESIGN.md)** - Complete worker architecture guide
599+
- AsyncTaskRunner vs TaskRunner
600+
- Automatic runner selection (`def` vs `async def`)
601+
- Performance comparison and best practices
602+
- Worker discovery and metrics
603+
604+
- **[Examples](examples/)** - Working examples with configuration
605+
- `examples/worker_configuration_example.py` - Hierarchical configuration demo
606+
- `examples/workers_e2e.py` - End-to-end example
607+
- `examples/asyncio_workers.py` - Mixed sync/async workers
608+
609+
---
470610

471-
Configuration priority: **Worker-specific** > **Global** > **Code defaults**
611+
**Last Updated**: 2025-11-28
612+
**SDK Version**: 1.3.0+

0 commit comments

Comments
 (0)