gooddata-flight-server 1.34.1.dev1__py3-none-any.whl
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.
Potentially problematic release.
This version of gooddata-flight-server might be problematic. Click here for more details.
- gooddata_flight_server/__init__.py +23 -0
- gooddata_flight_server/_version.py +7 -0
- gooddata_flight_server/cli.py +137 -0
- gooddata_flight_server/config/__init__.py +1 -0
- gooddata_flight_server/config/config.py +536 -0
- gooddata_flight_server/errors/__init__.py +1 -0
- gooddata_flight_server/errors/error_code.py +209 -0
- gooddata_flight_server/errors/error_info.py +475 -0
- gooddata_flight_server/exceptions.py +16 -0
- gooddata_flight_server/health/__init__.py +1 -0
- gooddata_flight_server/health/health_check_http_server.py +103 -0
- gooddata_flight_server/health/server_health_monitor.py +83 -0
- gooddata_flight_server/metrics.py +16 -0
- gooddata_flight_server/py.typed +1 -0
- gooddata_flight_server/server/__init__.py +1 -0
- gooddata_flight_server/server/auth/__init__.py +1 -0
- gooddata_flight_server/server/auth/auth_middleware.py +83 -0
- gooddata_flight_server/server/auth/token_verifier.py +62 -0
- gooddata_flight_server/server/auth/token_verifier_factory.py +55 -0
- gooddata_flight_server/server/auth/token_verifier_impl.py +41 -0
- gooddata_flight_server/server/base.py +63 -0
- gooddata_flight_server/server/default.logging.ini +28 -0
- gooddata_flight_server/server/flight_rpc/__init__.py +1 -0
- gooddata_flight_server/server/flight_rpc/flight_middleware.py +162 -0
- gooddata_flight_server/server/flight_rpc/flight_server.py +228 -0
- gooddata_flight_server/server/flight_rpc/flight_service.py +279 -0
- gooddata_flight_server/server/flight_rpc/server_methods.py +200 -0
- gooddata_flight_server/server/server_base.py +321 -0
- gooddata_flight_server/server/server_main.py +116 -0
- gooddata_flight_server/tasks/__init__.py +1 -0
- gooddata_flight_server/tasks/base.py +21 -0
- gooddata_flight_server/tasks/metrics.py +115 -0
- gooddata_flight_server/tasks/task.py +193 -0
- gooddata_flight_server/tasks/task_error.py +60 -0
- gooddata_flight_server/tasks/task_executor.py +96 -0
- gooddata_flight_server/tasks/task_result.py +363 -0
- gooddata_flight_server/tasks/temporal_container.py +247 -0
- gooddata_flight_server/tasks/thread_task_executor.py +639 -0
- gooddata_flight_server/utils/__init__.py +1 -0
- gooddata_flight_server/utils/libc_utils.py +35 -0
- gooddata_flight_server/utils/logging.py +158 -0
- gooddata_flight_server/utils/methods_discovery.py +98 -0
- gooddata_flight_server/utils/otel_tracing.py +142 -0
- gooddata_flight_server-1.34.1.dev1.data/scripts/gooddata-flight-server +10 -0
- gooddata_flight_server-1.34.1.dev1.dist-info/LICENSE.txt +7 -0
- gooddata_flight_server-1.34.1.dev1.dist-info/METADATA +749 -0
- gooddata_flight_server-1.34.1.dev1.dist-info/RECORD +49 -0
- gooddata_flight_server-1.34.1.dev1.dist-info/WHEEL +5 -0
- gooddata_flight_server-1.34.1.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# (C) 2024 GoodData Corporation
|
|
2
|
+
import threading
|
|
3
|
+
from typing import Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
from prometheus_client import Counter, Gauge, Summary
|
|
6
|
+
from prometheus_client.metrics import MetricWrapperBase
|
|
7
|
+
|
|
8
|
+
_TMetric = TypeVar("_TMetric", bound=MetricWrapperBase)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TaskExecutorMetrics:
|
|
12
|
+
"""
|
|
13
|
+
Facade to access prometheus metrics that the task executor maintains.
|
|
14
|
+
|
|
15
|
+
Note that this is somewhat more convoluted because:
|
|
16
|
+
|
|
17
|
+
1. The TaskExecutor can produce metrics for various task types so metric names have to
|
|
18
|
+
be variable (based on prefix)
|
|
19
|
+
|
|
20
|
+
2. Prometheus does not like double-registration of metrics - this is something that
|
|
21
|
+
definitely happens during various tests.
|
|
22
|
+
|
|
23
|
+
Thus, for each metric, the class maintains a static mapping (prefix -> actual instance) and
|
|
24
|
+
every time the class is instantiated with particular prefix, the constructor will get existing
|
|
25
|
+
or create new instances.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
_QueueSize: dict[str, Gauge] = {}
|
|
29
|
+
_CloseQueueSize: dict[str, Gauge] = {}
|
|
30
|
+
_WaitTime: dict[str, Summary] = {}
|
|
31
|
+
_TaskE2EDuration: dict[str, Summary] = {}
|
|
32
|
+
_TaskDuration: dict[str, Summary] = {}
|
|
33
|
+
_TaskErrors: dict[str, Counter] = {}
|
|
34
|
+
_TaskCancelled: dict[str, Counter] = {}
|
|
35
|
+
_TaskCompleted: dict[str, Counter] = {}
|
|
36
|
+
_MapLock = threading.Lock()
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _get_or_create(d: dict[str, _TMetric], prefix: str, create_fun: Callable[[], _TMetric]) -> _TMetric:
|
|
40
|
+
with TaskExecutorMetrics._MapLock:
|
|
41
|
+
existing = d.get(prefix)
|
|
42
|
+
if existing is not None:
|
|
43
|
+
return existing
|
|
44
|
+
|
|
45
|
+
new = create_fun()
|
|
46
|
+
d[prefix] = new
|
|
47
|
+
return new
|
|
48
|
+
|
|
49
|
+
def __init__(self, prefix: str) -> None:
|
|
50
|
+
self._prefix = prefix
|
|
51
|
+
|
|
52
|
+
self.queue_size = self._get_or_create(
|
|
53
|
+
TaskExecutorMetrics._QueueSize,
|
|
54
|
+
prefix,
|
|
55
|
+
lambda: Gauge(f"{prefix}_task_queue", "Number of tasks waiting in queue."),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self.close_queue_size = self._get_or_create(
|
|
59
|
+
TaskExecutorMetrics._CloseQueueSize,
|
|
60
|
+
prefix,
|
|
61
|
+
lambda: Gauge(
|
|
62
|
+
f"{prefix}_close_queue",
|
|
63
|
+
"Number of task execution results waiting in the queue to be closed and cleaned up.",
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
self.wait_time = self._get_or_create(
|
|
68
|
+
TaskExecutorMetrics._WaitTime,
|
|
69
|
+
prefix,
|
|
70
|
+
lambda: Summary(
|
|
71
|
+
f"{prefix}_task_wait",
|
|
72
|
+
"Time a task spends waiting in queue before it is executed.",
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.task_duration = self._get_or_create(
|
|
77
|
+
TaskExecutorMetrics._TaskDuration,
|
|
78
|
+
prefix,
|
|
79
|
+
lambda: Summary(
|
|
80
|
+
f"{prefix}_task_duration",
|
|
81
|
+
"Duration of task run itself (does not include wait or prerequisite resolution duration).",
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self.task_e2e_duration = self._get_or_create(
|
|
86
|
+
TaskExecutorMetrics._TaskE2EDuration,
|
|
87
|
+
prefix,
|
|
88
|
+
lambda: Summary(
|
|
89
|
+
f"{prefix}_task_e2e_duration",
|
|
90
|
+
"End-to-end duration of the task execution. Includes prerequisite resolution duration and "
|
|
91
|
+
"time spent in queue. This is the duration as observed by the callers.",
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.task_errors = self._get_or_create(
|
|
96
|
+
TaskExecutorMetrics._TaskErrors,
|
|
97
|
+
prefix,
|
|
98
|
+
lambda: Counter(f"{prefix}_task_error", "Number of failed tasks."),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
self.task_cancelled = self._get_or_create(
|
|
102
|
+
TaskExecutorMetrics._TaskCancelled,
|
|
103
|
+
prefix,
|
|
104
|
+
lambda: Counter(f"{prefix}_task_cancelled", "Number of cancelled tasks."),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self.task_completed = self._get_or_create(
|
|
108
|
+
TaskExecutorMetrics._TaskCompleted,
|
|
109
|
+
prefix,
|
|
110
|
+
lambda: Counter(
|
|
111
|
+
f"{prefix}_task_completed",
|
|
112
|
+
"Number of completed tasks - this includes all tasks regardless "
|
|
113
|
+
"of how their execution completed (success, failure, cancel).",
|
|
114
|
+
),
|
|
115
|
+
)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# (C) 2024 GoodData Corporation
|
|
2
|
+
import abc
|
|
3
|
+
import threading
|
|
4
|
+
import uuid
|
|
5
|
+
from concurrent.futures import CancelledError
|
|
6
|
+
from typing import Optional, Union, final
|
|
7
|
+
|
|
8
|
+
from gooddata_flight_server.tasks.task_error import TaskError
|
|
9
|
+
from gooddata_flight_server.tasks.task_result import TaskResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Task(abc.ABC):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for executable tasks.
|
|
15
|
+
|
|
16
|
+
This class provides the essential boilerplate and declares a single `run` method which
|
|
17
|
+
should be implemented by subclasses. The `run` does the actual work and returns its
|
|
18
|
+
result.
|
|
19
|
+
|
|
20
|
+
The Task design is such that it allows for runtime cancel-ability:
|
|
21
|
+
|
|
22
|
+
- task can be flagged as cancellable or not (default is True)
|
|
23
|
+
- when cancellable, the `cancel` method can be used to indicate that the task
|
|
24
|
+
should cancel
|
|
25
|
+
- this turns on the cancelled indicator
|
|
26
|
+
|
|
27
|
+
A cancellable task should test the cancelled indicator after each significant
|
|
28
|
+
step and bail-out by raising CancelledError
|
|
29
|
+
|
|
30
|
+
If the `run` method is entering a point of no return (e.g. cancel / rollback is
|
|
31
|
+
no longer feasible), then it must first switch the task to be non-cancellable
|
|
32
|
+
using the `switch_non_cancellable` - this may raise CancelledError if the `run`
|
|
33
|
+
was raced and someone cancelled the task.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__slots__ = (
|
|
37
|
+
"_task_id",
|
|
38
|
+
"_cmd",
|
|
39
|
+
"_cancel_lock",
|
|
40
|
+
"_cancelled",
|
|
41
|
+
"_cancellable",
|
|
42
|
+
"_triggers",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
cmd: bytes,
|
|
48
|
+
cancellable: bool = True,
|
|
49
|
+
task_id: Optional[str] = None,
|
|
50
|
+
):
|
|
51
|
+
self._task_id = task_id or uuid.uuid4().hex
|
|
52
|
+
self._cmd = cmd
|
|
53
|
+
self._cancel_lock = threading.Lock()
|
|
54
|
+
self._cancelled = False
|
|
55
|
+
self._cancellable = cancellable
|
|
56
|
+
|
|
57
|
+
@final
|
|
58
|
+
@property
|
|
59
|
+
def task_id(self) -> str:
|
|
60
|
+
return self._task_id
|
|
61
|
+
|
|
62
|
+
@final
|
|
63
|
+
@property
|
|
64
|
+
def cmd(self) -> bytes:
|
|
65
|
+
return self._cmd
|
|
66
|
+
|
|
67
|
+
@final
|
|
68
|
+
@property
|
|
69
|
+
def cancelled(self) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
:return: true if the running task was cancelled
|
|
72
|
+
"""
|
|
73
|
+
with self._cancel_lock:
|
|
74
|
+
return self._cancelled
|
|
75
|
+
|
|
76
|
+
def check_cancelled(self) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Checks whether task got cancelled - if so, raises CancelledError.
|
|
79
|
+
|
|
80
|
+
This is utility method that may be used in Task.run() to perform
|
|
81
|
+
cancellation checks.
|
|
82
|
+
|
|
83
|
+
:return: nothing
|
|
84
|
+
"""
|
|
85
|
+
if self.cancelled:
|
|
86
|
+
raise CancelledError()
|
|
87
|
+
|
|
88
|
+
@final
|
|
89
|
+
def cancel(self) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Try to cancel an *already running* task. Depending on the state of the task,
|
|
92
|
+
this may or may not be possible.
|
|
93
|
+
|
|
94
|
+
If the cancel succeeds, it is guaranteed that the task has no side-effects on
|
|
95
|
+
the rest of the system - it is as if it never run.
|
|
96
|
+
|
|
97
|
+
:return: True if cancel was successful, False if not
|
|
98
|
+
"""
|
|
99
|
+
with self._cancel_lock:
|
|
100
|
+
if not self._cancellable:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
first_cancel = not self._cancelled
|
|
104
|
+
self._cancelled = True
|
|
105
|
+
|
|
106
|
+
if first_cancel:
|
|
107
|
+
try:
|
|
108
|
+
self.on_task_cancel()
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
@final
|
|
115
|
+
def switch_non_cancellable(self) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Switch the task to non-cancellable state.
|
|
118
|
+
|
|
119
|
+
If the task got cancelled, raises CancelledError() at this point.
|
|
120
|
+
Otherwise, sets the non-cancellable flag and returns.
|
|
121
|
+
|
|
122
|
+
:return: nothing
|
|
123
|
+
:raises: CancelledError if the switch is not possible because the task got cancelled already
|
|
124
|
+
"""
|
|
125
|
+
with self._cancel_lock:
|
|
126
|
+
if self._cancelled:
|
|
127
|
+
raise CancelledError()
|
|
128
|
+
|
|
129
|
+
self._cancellable = False
|
|
130
|
+
|
|
131
|
+
def on_task_cancel(self) -> None:
|
|
132
|
+
"""
|
|
133
|
+
This method will be called when a task is cancelled. That is, when it is still in
|
|
134
|
+
cancellable state and someone calls the cancel() for the first time.
|
|
135
|
+
|
|
136
|
+
The concrete implementation may optionally override this method to do something
|
|
137
|
+
special on cancellation - like cascading the cancellation to further sub-components.
|
|
138
|
+
|
|
139
|
+
Important: this method should not block.
|
|
140
|
+
|
|
141
|
+
:return: nothing
|
|
142
|
+
"""
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
def on_task_error(self, error: TaskError) -> Optional[TaskError]:
|
|
146
|
+
"""
|
|
147
|
+
This method will be called when a task fails with and raises an exception. It
|
|
148
|
+
will be called after executor creates an instance of TaskError from the
|
|
149
|
+
exception, and BEFORE it performs logging / tracking of the exception.
|
|
150
|
+
|
|
151
|
+
The concrete implementation may optionally override this method to do something
|
|
152
|
+
with the TaskError that was created by the executor. For example:
|
|
153
|
+
|
|
154
|
+
- intercept automatically generated TaskError and replace / modify it
|
|
155
|
+
(for example categorize client errors)
|
|
156
|
+
|
|
157
|
+
- do custom logging / tracking of the error
|
|
158
|
+
|
|
159
|
+
For convenience, if this method returns None, the executor will use the original
|
|
160
|
+
task error instance as-is.
|
|
161
|
+
|
|
162
|
+
:param error: TaskError as categorized by the executor
|
|
163
|
+
:return: None if the original `error` should be used, otherwise an instance of
|
|
164
|
+
TaskError
|
|
165
|
+
"""
|
|
166
|
+
return error
|
|
167
|
+
|
|
168
|
+
@abc.abstractmethod
|
|
169
|
+
def run(self) -> Union[TaskResult, TaskError]:
|
|
170
|
+
"""
|
|
171
|
+
Runs the task.
|
|
172
|
+
|
|
173
|
+
This method should be implemented by subclasses and do work according to payload
|
|
174
|
+
included in the `cmd`. Upon successful completion, the method should return a
|
|
175
|
+
TaskResult - either FlightPathTaskResult (when task produced a flight path) or
|
|
176
|
+
FlightDataTaskResult (when task created a live result).
|
|
177
|
+
|
|
178
|
+
Upon failure, the task has two options - use whichever is more convenient:
|
|
179
|
+
|
|
180
|
+
- either raise an exception: in this case the TaskExecutor will analyze and
|
|
181
|
+
convert the exception to TaskError (with error codes and everything) using
|
|
182
|
+
the built-in logic; the Task's `on_task_error` method will be called with
|
|
183
|
+
the TaskError created using the standard error handling logic
|
|
184
|
+
|
|
185
|
+
- return TaskError: this will be used by the TaskExecutor as-is. This option
|
|
186
|
+
is useful in situations when the task wants to do more elaborate
|
|
187
|
+
error handling / logging / reporting.
|
|
188
|
+
|
|
189
|
+
:return: result of the task
|
|
190
|
+
:raise Exception
|
|
191
|
+
:raise CancelledError: when the task's run was cancelled
|
|
192
|
+
"""
|
|
193
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# (C) 2024 GoodData Corporation
|
|
2
|
+
import dataclasses
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable, Optional
|
|
5
|
+
|
|
6
|
+
import pyarrow.flight
|
|
7
|
+
|
|
8
|
+
from gooddata_flight_server.errors.error_info import ErrorInfo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class TaskError:
|
|
13
|
+
"""
|
|
14
|
+
Detail about failed task execution. The original Exception that was raised and
|
|
15
|
+
failed the task is intentionally _not_ stored here.
|
|
16
|
+
|
|
17
|
+
That is because by storing the exception and the included traceback, the code
|
|
18
|
+
would also hold onto stack frames and all local variables bound to them - which
|
|
19
|
+
can in return hold onto _a lot_ of memory.
|
|
20
|
+
|
|
21
|
+
See: https://cosmicpercolator.com/2016/01/13/exception-leaks-in-python-2-and-3/
|
|
22
|
+
See also: https://github.com/apache/arrow/issues/36540
|
|
23
|
+
|
|
24
|
+
Also note, that clearing the traceback as hinted in above article helps somewhat
|
|
25
|
+
but is not ideal when working with FlightErrors. While testing and measuring, I
|
|
26
|
+
have found that even a freshly constructed FlightError (for example a freshly constructed
|
|
27
|
+
copy of the original exception's message + extra_info) has some non-trivial overhead.
|
|
28
|
+
Unsure why is that, and I'm not going to spend more time to investigate :)
|
|
29
|
+
|
|
30
|
+
Therefore, I have converged to this approach where the task error contains a
|
|
31
|
+
ErrorInfo (all essential detail) and a factory function to create the
|
|
32
|
+
actual FlightError.
|
|
33
|
+
|
|
34
|
+
The code that is supposed to raise the actual exception to the client will
|
|
35
|
+
instantiate the exception when needed.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
error_info: ErrorInfo
|
|
39
|
+
error_factory: Callable[[str, Optional[bytes]], pyarrow.flight.FlightError]
|
|
40
|
+
|
|
41
|
+
client_error: bool = False
|
|
42
|
+
"""
|
|
43
|
+
indicates whether the task failed because client provided invalid input.
|
|
44
|
+
|
|
45
|
+
this will be used purely for logging / tracking purposes. e.g. tasks failed due to client
|
|
46
|
+
providing bad input are logged as info and the task executor does not bump error counter
|
|
47
|
+
metrics
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def as_flight_error(self) -> pyarrow.flight.FlightError:
|
|
51
|
+
"""
|
|
52
|
+
:return: new instance of FlightError that should be raised
|
|
53
|
+
"""
|
|
54
|
+
return self.error_info.to_flight_error(self.error_factory)
|
|
55
|
+
|
|
56
|
+
def to_client_error(self) -> "TaskError":
|
|
57
|
+
"""
|
|
58
|
+
:return: creates a copy of this instance with `client_error` indicator set to True
|
|
59
|
+
"""
|
|
60
|
+
return dataclasses.replace(self, client_error=True)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# (C) 2024 GoodData Corporation
|
|
2
|
+
import abc
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from gooddata_flight_server.tasks.task import Task
|
|
6
|
+
from gooddata_flight_server.tasks.task_result import TaskExecutionResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TaskAttributes:
|
|
10
|
+
TaskId = "gooddata_flight_server.task_id"
|
|
11
|
+
TaskCancelled = "gooddata_flight_server.task_cancelled"
|
|
12
|
+
TaskError = "gooddata_flight_server.task_error"
|
|
13
|
+
TaskErrorCode = "gooddata_flight_server.task_error.code"
|
|
14
|
+
TaskErrorMsg = "gooddata_flight_server.task_error.msg"
|
|
15
|
+
TaskErrorDetail = "gooddata_flight_server.task_error.detail"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TaskExecutor(abc.ABC):
|
|
19
|
+
"""
|
|
20
|
+
Declares interface for Task Executors. These allow asynchronous execution
|
|
21
|
+
of tasks which 'somehow' generate flight data.
|
|
22
|
+
|
|
23
|
+
The methods on this interface are designed to support a pollable
|
|
24
|
+
GetFlightInfo -> DoGet flows. A task can be submitted, polled for
|
|
25
|
+
completion or cancelled.
|
|
26
|
+
|
|
27
|
+
Once the task finishes (with any outcome), a TaskExecutionResult is available
|
|
28
|
+
and describes the outcome. On success, the execution result contains
|
|
29
|
+
a reference to task's actual result.
|
|
30
|
+
|
|
31
|
+
The execution result and the task's actual result are retained in the
|
|
32
|
+
executor for a limited (configurable) amount of time.
|
|
33
|
+
|
|
34
|
+
The actual task result is either:
|
|
35
|
+
|
|
36
|
+
- FlightDataTaskResult - which represents data that was 'generated' by the task
|
|
37
|
+
somehow and is available for single or repeated reads
|
|
38
|
+
- FlightPathTaskResult - which represent data stored under some flight path;
|
|
39
|
+
typically, flight commands include option to sink result under a flight path and
|
|
40
|
+
if that is the case the task will generate data and store it and then return
|
|
41
|
+
the pointer
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@abc.abstractmethod
|
|
45
|
+
def submit(
|
|
46
|
+
self,
|
|
47
|
+
task: Task,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Submit a new task that will perform all work as described in the provided command.
|
|
51
|
+
|
|
52
|
+
:param task: task to run
|
|
53
|
+
:return: nothing, task is always submitted
|
|
54
|
+
"""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
@abc.abstractmethod
|
|
58
|
+
def wait_for_result(self, task_id: str, timeout: Optional[float] = None) -> Optional[TaskExecutionResult]:
|
|
59
|
+
"""
|
|
60
|
+
Wait for the task with the provided task id to finish.
|
|
61
|
+
|
|
62
|
+
If a task already finished and this service still has records describing it's result, then
|
|
63
|
+
the saved result is returned immediately.
|
|
64
|
+
|
|
65
|
+
Otherwise, if a task is pending, the method will block (optionally with timeout) until
|
|
66
|
+
the task completes. If it does not complete in given timeframe, the code will raise TimeoutError.
|
|
67
|
+
|
|
68
|
+
Note: if the task was cancelled, the result will have 'cancelled' indicator flag set to True.
|
|
69
|
+
|
|
70
|
+
:param task_id: task id to wait for
|
|
71
|
+
:param timeout: time to wait for completion
|
|
72
|
+
:raise TaskWaitTimeoutError: if the wait for task completion timed out
|
|
73
|
+
:return: result or None if there is no such task
|
|
74
|
+
"""
|
|
75
|
+
raise NotImplementedError
|
|
76
|
+
|
|
77
|
+
@abc.abstractmethod
|
|
78
|
+
def cancel(self, task_id: str) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
Try to cancel a task - either by dropping it from the task queue or by cancelling
|
|
81
|
+
a running task or dropping a result of already finished task.
|
|
82
|
+
|
|
83
|
+
:param task_id: task id to cancel
|
|
84
|
+
:return: true if cancelled, false if cancel not possible (no such task or task not cancellable anymore)
|
|
85
|
+
"""
|
|
86
|
+
raise NotImplementedError
|
|
87
|
+
|
|
88
|
+
@abc.abstractmethod
|
|
89
|
+
def close_result(self, task_id: str) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Try to close result of a previously completed task.
|
|
92
|
+
|
|
93
|
+
:param task_id: task id, whose result to close
|
|
94
|
+
:return: true if result closed, false if no result for that task id
|
|
95
|
+
"""
|
|
96
|
+
raise NotImplementedError
|