docs
0
# Polling and Long-Running Operations
1
2
Azure Core provides polling mechanisms for handling Azure long-running operations (LROs) with customizable polling strategies, automatic status checking, and comprehensive async support. This enables consistent handling of operations that may take minutes or hours to complete.
3
4
## Core Polling Classes
5
6
Base classes for implementing polling functionality for both synchronous and asynchronous operations.
7
8
```python { .api }
9
from azure.core.polling import LROPoller, AsyncLROPoller, PollingMethod, AsyncPollingMethod
10
from typing import Optional, Any, Callable
11
12
class LROPoller:
13
"""Synchronous long-running operation poller."""
14
def __init__(
15
self,
16
client: Any,
17
initial_response: Any,
18
deserialization_callback: Callable,
19
polling_method: PollingMethod,
20
**kwargs
21
): ...
22
23
def result(self, timeout: Optional[float] = None) -> Any:
24
"""Return the result of the long-running operation."""
25
...
26
27
def wait(self, timeout: Optional[float] = None) -> None:
28
"""Wait for the operation to complete."""
29
...
30
31
def done(self) -> bool:
32
"""Check if the operation has completed."""
33
...
34
35
def status(self) -> str:
36
"""Get the current status of the operation."""
37
...
38
39
def polling_method(self) -> PollingMethod:
40
"""Get the polling method being used."""
41
...
42
43
def continuation_token(self) -> str:
44
"""Get continuation token for resuming the operation."""
45
...
46
47
class AsyncLROPoller:
48
"""Asynchronous long-running operation poller."""
49
def __init__(
50
self,
51
client: Any,
52
initial_response: Any,
53
deserialization_callback: Callable,
54
polling_method: AsyncPollingMethod,
55
**kwargs
56
): ...
57
58
async def result(self, timeout: Optional[float] = None) -> Any:
59
"""Return the result of the long-running operation."""
60
...
61
62
async def wait(self, timeout: Optional[float] = None) -> None:
63
"""Wait for the operation to complete."""
64
...
65
66
def done(self) -> bool:
67
"""Check if the operation has completed."""
68
...
69
70
def status(self) -> str:
71
"""Get the current status of the operation."""
72
...
73
74
def polling_method(self) -> AsyncPollingMethod:
75
"""Get the polling method being used."""
76
...
77
78
def continuation_token(self) -> str:
79
"""Get continuation token for resuming the operation."""
80
...
81
```
82
83
## Polling Method Interfaces
84
85
Base interfaces for implementing custom polling strategies.
86
87
```python { .api }
88
from abc import ABC, abstractmethod
89
90
class PollingMethod(ABC):
91
"""Base synchronous polling method interface."""
92
@abstractmethod
93
def initialize(self, client: Any, initial_response: Any, deserialization_callback: Callable) -> None:
94
"""Initialize the polling method."""
95
...
96
97
@abstractmethod
98
def run(self) -> None:
99
"""Execute polling until completion."""
100
...
101
102
@abstractmethod
103
def status(self) -> str:
104
"""Get current operation status."""
105
...
106
107
@abstractmethod
108
def finished(self) -> bool:
109
"""Check if operation is finished."""
110
...
111
112
@abstractmethod
113
def resource(self) -> Any:
114
"""Get the final resource."""
115
...
116
117
class AsyncPollingMethod(ABC):
118
"""Base asynchronous polling method interface."""
119
@abstractmethod
120
async def initialize(self, client: Any, initial_response: Any, deserialization_callback: Callable) -> None:
121
"""Initialize the polling method."""
122
...
123
124
@abstractmethod
125
async def run(self) -> None:
126
"""Execute polling until completion."""
127
...
128
129
@abstractmethod
130
def status(self) -> str:
131
"""Get current operation status."""
132
...
133
134
@abstractmethod
135
def finished(self) -> bool:
136
"""Check if operation is finished."""
137
...
138
139
@abstractmethod
140
def resource(self) -> Any:
141
"""Get the final resource."""
142
...
143
```
144
145
## Capabilities
146
147
### No-Operation Polling
148
149
For operations that complete immediately or don't require polling.
150
151
```python { .api }
152
from azure.core.polling import NoPolling, AsyncNoPolling
153
154
class NoPolling(PollingMethod):
155
"""No-op polling implementation for immediate operations."""
156
def __init__(self): ...
157
158
class AsyncNoPolling(AsyncPollingMethod):
159
"""Async no-op polling implementation for immediate operations."""
160
def __init__(self): ...
161
```
162
163
### Async Poller Utilities
164
165
Utility functions for working with async pollers.
166
167
```python { .api }
168
from azure.core.polling import async_poller
169
170
def async_poller(func):
171
"""Decorator for creating async pollers from sync polling methods."""
172
...
173
```
174
175
## Usage Examples
176
177
### Basic Synchronous Polling
178
179
```python
180
from azure.core.polling import LROPoller
181
from azure.core import PipelineClient
182
import time
183
184
def start_long_running_operation():
185
"""Example of starting and polling a long-running operation."""
186
client = PipelineClient(base_url="https://api.example.com")
187
188
# Start the operation (this would be service-specific)
189
initial_response = client.send_request("POST", "/operations/create")
190
191
# Create poller (in real usage, this would be created by the service client)
192
# poller = LROPoller(client, initial_response, deserialize_callback, polling_method)
193
194
# Wait for completion
195
# result = poller.result(timeout=300) # 5 minute timeout
196
# print(f"Operation completed with result: {result}")
197
198
return initial_response
199
200
def check_operation_status(poller):
201
"""Check the status of an ongoing operation."""
202
print(f"Current status: {poller.status()}")
203
print(f"Is done: {poller.done()}")
204
205
if not poller.done():
206
print("Operation still in progress...")
207
# Could wait more or check later
208
poller.wait(timeout=60) # Wait up to 1 minute
209
210
if poller.done():
211
result = poller.result()
212
print(f"Operation completed: {result}")
213
```
214
215
### Asynchronous Polling
216
217
```python
218
import asyncio
219
from azure.core.polling import AsyncLROPoller
220
221
async def start_async_long_running_operation():
222
"""Example of async long-running operation polling."""
223
# client = AsyncPipelineClient(base_url="https://api.example.com")
224
225
# Start the operation
226
# initial_response = await client.send_request("POST", "/operations/create")
227
228
# Create async poller
229
# poller = AsyncLROPoller(client, initial_response, deserialize_callback, async_polling_method)
230
231
# Wait for completion asynchronously
232
# result = await poller.result(timeout=300)
233
# print(f"Async operation completed: {result}")
234
235
pass
236
237
async def monitor_operation_progress(poller):
238
"""Monitor operation progress with periodic status checks."""
239
while not poller.done():
240
print(f"Status: {poller.status()}")
241
await asyncio.sleep(5) # Check every 5 seconds
242
243
result = await poller.result()
244
print(f"Operation completed: {result}")
245
246
# asyncio.run(start_async_long_running_operation())
247
```
248
249
### Resuming Operations with Continuation Tokens
250
251
```python
252
def resume_operation_from_token(continuation_token):
253
"""Resume a long-running operation using a continuation token."""
254
# In real usage, you would recreate the poller from the token
255
# poller = service_client.resume_operation(continuation_token)
256
257
# Check current status
258
# print(f"Resumed operation status: {poller.status()}")
259
260
# Continue waiting for completion
261
# result = poller.result()
262
# return result
263
264
pass
265
266
def save_operation_state(poller):
267
"""Save operation state for later resumption."""
268
if not poller.done():
269
token = poller.continuation_token()
270
# Save token to database or file for later use
271
print(f"Operation token saved: {token}")
272
return token
273
return None
274
```
275
276
### Error Handling with Polling
277
278
```python
279
from azure.core.exceptions import AzureError, HttpResponseError
280
281
def poll_with_error_handling(poller):
282
"""Handle errors during polling operations."""
283
try:
284
# Wait for operation with timeout
285
result = poller.result(timeout=600) # 10 minute timeout
286
print(f"Operation succeeded: {result}")
287
return result
288
289
except HttpResponseError as e:
290
if e.status_code == 404:
291
print("Operation not found - may have been cancelled")
292
elif e.status_code >= 500:
293
print(f"Server error during operation: {e.status_code}")
294
else:
295
print(f"HTTP error: {e.status_code} - {e.reason}")
296
raise
297
298
except AzureError as e:
299
print(f"Azure service error: {e.message}")
300
301
# Check if operation can be resumed
302
if hasattr(e, 'continuation_token') and e.continuation_token:
303
print("Operation can be resumed with token:", e.continuation_token)
304
raise
305
306
except TimeoutError:
307
print("Operation timed out")
308
309
# Save state for later resumption
310
if not poller.done():
311
token = poller.continuation_token()
312
print(f"Save this token to resume later: {token}")
313
raise
314
```
315
316
### Custom Polling Strategy
317
318
```python
319
from azure.core.polling import PollingMethod
320
import time
321
322
class CustomPollingMethod(PollingMethod):
323
"""Custom polling method with exponential backoff."""
324
325
def __init__(self, initial_delay=1, max_delay=60, backoff_factor=2):
326
self.initial_delay = initial_delay
327
self.max_delay = max_delay
328
self.backoff_factor = backoff_factor
329
self.current_delay = initial_delay
330
self._client = None
331
self._operation_url = None
332
self._finished = False
333
self._status = "InProgress"
334
self._resource = None
335
336
def initialize(self, client, initial_response, deserialization_callback):
337
self._client = client
338
self._deserialize = deserialization_callback
339
340
# Extract operation URL from initial response
341
if hasattr(initial_response, 'headers'):
342
self._operation_url = initial_response.headers.get('Location')
343
344
# Check if already completed
345
if initial_response.status_code in [200, 201]:
346
self._finished = True
347
self._status = "Succeeded"
348
self._resource = self._deserialize(initial_response)
349
350
def run(self):
351
while not self._finished:
352
time.sleep(self.current_delay)
353
354
# Poll the operation status
355
response = self._client.send_request("GET", self._operation_url)
356
357
if response.status_code == 200:
358
# Operation completed successfully
359
self._finished = True
360
self._status = "Succeeded"
361
self._resource = self._deserialize(response)
362
elif response.status_code == 202:
363
# Still in progress
364
self._status = "InProgress"
365
366
# Increase delay for next poll (exponential backoff)
367
self.current_delay = min(
368
self.current_delay * self.backoff_factor,
369
self.max_delay
370
)
371
else:
372
# Error occurred
373
self._finished = True
374
self._status = "Failed"
375
raise HttpResponseError(f"Operation failed: {response.status_code}")
376
377
def status(self):
378
return self._status
379
380
def finished(self):
381
return self._finished
382
383
def resource(self):
384
return self._resource
385
386
# Usage
387
def use_custom_polling():
388
custom_method = CustomPollingMethod(initial_delay=2, max_delay=30)
389
# poller = LROPoller(client, initial_response, deserialize_func, custom_method)
390
# result = poller.result()
391
```
392
393
### Async Custom Polling
394
395
```python
396
import asyncio
397
from azure.core.polling import AsyncPollingMethod
398
399
class AsyncCustomPollingMethod(AsyncPollingMethod):
400
"""Custom async polling method."""
401
402
def __init__(self, initial_delay=1, max_delay=60):
403
self.initial_delay = initial_delay
404
self.max_delay = max_delay
405
self.current_delay = initial_delay
406
self._client = None
407
self._operation_url = None
408
self._finished = False
409
self._status = "InProgress"
410
self._resource = None
411
412
async def initialize(self, client, initial_response, deserialization_callback):
413
self._client = client
414
self._deserialize = deserialization_callback
415
416
if hasattr(initial_response, 'headers'):
417
self._operation_url = initial_response.headers.get('Location')
418
419
if initial_response.status_code in [200, 201]:
420
self._finished = True
421
self._status = "Succeeded"
422
self._resource = self._deserialize(initial_response)
423
424
async def run(self):
425
while not self._finished:
426
await asyncio.sleep(self.current_delay)
427
428
response = await self._client.send_request("GET", self._operation_url)
429
430
if response.status_code == 200:
431
self._finished = True
432
self._status = "Succeeded"
433
self._resource = self._deserialize(response)
434
elif response.status_code == 202:
435
self._status = "InProgress"
436
self.current_delay = min(self.current_delay * 2, self.max_delay)
437
else:
438
self._finished = True
439
self._status = "Failed"
440
raise HttpResponseError(f"Operation failed: {response.status_code}")
441
442
def status(self):
443
return self._status
444
445
def finished(self):
446
return self._finished
447
448
def resource(self):
449
return self._resource
450
```
451
452
## Best Practices
453
454
### Polling Configuration
455
456
- Set appropriate timeouts based on expected operation duration
457
- Use exponential backoff to reduce server load
458
- Implement maximum retry limits to prevent infinite polling
459
- Consider operation complexity when setting initial polling intervals
460
461
### Resource Management
462
463
- Always use timeouts to prevent infinite waiting
464
- Save continuation tokens for long-running operations
465
- Properly handle poller cleanup in exception scenarios
466
- Use async pollers with async code for better resource utilization
467
468
### Error Handling
469
470
- Handle different types of errors appropriately (network, service, timeout)
471
- Implement retry logic for transient failures during polling
472
- Provide meaningful error messages to users
473
- Log polling progress and errors for debugging
474
475
### Performance Considerations
476
477
- Use appropriate polling intervals to balance responsiveness and server load
478
- Consider using webhooks or server-sent events for real-time updates when available
479
- Batch multiple operations when possible to reduce polling overhead
480
- Monitor polling patterns to optimize intervals for your specific use case