manta-node 0.5b0.dev91__py3-none-any.whl → 0.5b0.dev117__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.
@@ -10,7 +10,7 @@ import aiomqtt
10
10
  from manta_common.base_mqtt import MqttBase
11
11
  from manta_common.build.common.mqtt import MqMetricsSnapshot, MqTaskUpdate
12
12
  from manta_common.build.common.system import Logs
13
- from manta_common.build.common.tasks import TaskStatus, TaskUpdate
13
+ from manta_common.build.common.tasks import MqttTask, TaskStatus, TaskUpdate
14
14
  from manta_common.const import CHUNK_SIZE
15
15
  from manta_common.conversions import ID
16
16
  from manta_common.errors import (
@@ -121,52 +121,83 @@ class CommandHandler(TaskManager, Collector, MqttBase):
121
121
  except Exception:
122
122
  self.tracer.exception("Error when closing Docker client")
123
123
 
124
+ async def main(self):
125
+ """
126
+ Override MqttBase.main to run task checker as a background task
127
+ alongside the MQTT message loop.
128
+ """
129
+ self.tracer.debug("Starting background task checker")
130
+ checker = asyncio.create_task(self._ping_loop())
131
+ try:
132
+ await MqttBase.main(self)
133
+ finally:
134
+ checker.cancel()
135
+ try:
136
+ await checker
137
+ except asyncio.CancelledError:
138
+ pass
139
+
124
140
  async def loop_tasks(self):
125
141
  """
126
- Processes messages or check tasks to ping
142
+ MQTT message loop (subscribe + publish).
143
+ Task checking runs independently in the background via main().
144
+
145
+ Overrides base class to fix a race condition: the background
146
+ _ping_loop can queue messages during subscribe processing,
147
+ causing publish_task (queue.get()) to consume and remove them
148
+ from the queue. If the base class processes subscribe first
149
+ without also handling the completed publish_task, the message
150
+ is silently dropped. See issue #285.
127
151
  """
128
152
  while True:
129
- # Wait until we either (1) receive a message or (2) publish a message
130
153
  try:
131
154
  subscribe_task = asyncio.create_task(self.client.messages.__anext__())
132
155
  publish_task = asyncio.create_task(self.queue.get())
133
- ping_task = asyncio.create_task(self._ping())
134
156
  done, _ = await asyncio.wait(
135
- (subscribe_task, publish_task, ping_task),
157
+ (subscribe_task, publish_task),
136
158
  return_when=asyncio.FIRST_COMPLETED,
137
159
  )
138
- # If the asyncio.wait is cancelled, we must also cancel the queue task
139
160
  except asyncio.CancelledError:
140
161
  subscribe_task.cancel()
141
162
  publish_task.cancel()
142
- ping_task.cancel()
163
+ await asyncio.gather(
164
+ subscribe_task, publish_task, return_exceptions=True
165
+ )
143
166
  raise
144
167
 
145
- # When we receive a message, process it
146
- if subscribe_task in done and publish_task in done:
147
- await self._subscribe_from_task(subscribe_task)
148
- await self._publish_from_task(publish_task)
149
- ping_task.cancel()
150
- elif subscribe_task in done:
151
- await self._subscribe_from_task(subscribe_task)
152
- publish_task.cancel()
153
- ping_task.cancel()
154
- # Publish the message to its topic with specific QoS
155
- elif publish_task in done:
156
- await self._publish_from_task(publish_task)
157
- subscribe_task.cancel()
158
- ping_task.cancel()
159
- # If we disconnect from the broker, stop the loop with an exception
160
- elif ping_task in done:
161
- ping_task.cancel()
162
- publish_task.cancel()
163
- subscribe_task.cancel()
164
- else:
165
- self.tracer.warning("Disconnected during message iteration")
166
- publish_task.cancel()
167
- subscribe_task.cancel()
168
- ping_task.cancel()
169
- raise aiomqtt.MqttError("Disconnected during message iteration")
168
+ try:
169
+ if subscribe_task in done and publish_task in done:
170
+ try:
171
+ await self._subscribe_from_task(subscribe_task)
172
+ finally:
173
+ await self._publish_from_task(publish_task)
174
+ elif subscribe_task in done:
175
+ await self._subscribe_from_task(subscribe_task)
176
+ if publish_task.done():
177
+ await self._publish_from_task(publish_task)
178
+ elif publish_task in done:
179
+ await self._publish_from_task(publish_task)
180
+ else:
181
+ self.tracer.warning("Disconnected during message iteration")
182
+ raise aiomqtt.MqttError("Disconnected during message iteration")
183
+ finally:
184
+ if not subscribe_task.done():
185
+ subscribe_task.cancel()
186
+ if not publish_task.done():
187
+ publish_task.cancel()
188
+
189
+ async def _ping_loop(self):
190
+ """
191
+ Background loop that periodically checks task statuses and
192
+ sends heartbeats. Runs independently of the MQTT connection state.
193
+ """
194
+ while True:
195
+ try:
196
+ await self._ping()
197
+ except asyncio.CancelledError:
198
+ break
199
+ except Exception:
200
+ self.tracer.exception("Unhandled error in ping loop, continuing...")
170
201
 
171
202
  async def _ping(self):
172
203
  """
@@ -313,3 +344,32 @@ class CommandHandler(TaskManager, Collector, MqttBase):
313
344
  Node ID to stop
314
345
  """
315
346
  await self.stop_callback()
347
+
348
+ async def on_task_setup_failed(self, task: MqttTask, exc: Exception):
349
+ """
350
+ Publish a FAILED task update when task setup fails.
351
+
352
+ Parameters
353
+ ----------
354
+ task : MqttTask
355
+ Task that failed to setup
356
+ exc : Exception
357
+ The exception that caused the failure
358
+ """
359
+ task_update = TaskUpdate(
360
+ swarm_id=task.swarm_id,
361
+ node_id=self.tracer.metadata.node_id,
362
+ task_id=task.task_id,
363
+ iteration=task.iteration,
364
+ circular=task.circular,
365
+ task_status=TaskStatus.FAILED,
366
+ )
367
+ try:
368
+ await self.publish_task_update(task_update)
369
+ self.tracer.info(
370
+ f"Published FAILED status for task {task.task_id} after setup failure"
371
+ )
372
+ except Exception as publish_exc:
373
+ self.tracer.exception(
374
+ f"Failed to publish FAILED status for task {task.task_id}: {repr(publish_exc)}"
375
+ )
@@ -352,12 +352,31 @@ class TaskManager(ContainerManager, ImageExecutor):
352
352
  swarm_id = ID(task.swarm_id)
353
353
  self.tracer.info(f"Deploying task {task_id}")
354
354
 
355
- if (swarm_id.tid, task_id.tid) in self.tasks:
356
- await self.restart_task(self.tasks[(swarm_id.tid, task_id.tid)], task)
357
- else:
358
- await self.create_task(task)
355
+ try:
356
+ if (swarm_id.tid, task_id.tid) in self.tasks:
357
+ await self.restart_task(self.tasks[(swarm_id.tid, task_id.tid)], task)
358
+ else:
359
+ await self.create_task(task)
360
+ except Exception as exc:
361
+ self.tracer.exception(f"Task {task_id} setup failed: {repr(exc)}")
362
+ await self.on_task_setup_failed(task, exc)
363
+ raise
359
364
  return task_id
360
365
 
366
+ async def on_task_setup_failed(self, task: MqttTask, exc: Exception):
367
+ """
368
+ Hook called when task setup fails. Override in subclasses to handle
369
+ failure notifications (e.g., publish MQTT update).
370
+
371
+ Parameters
372
+ ----------
373
+ task : MqttTask
374
+ Task that failed to setup
375
+ exc : Exception
376
+ The exception that caused the failure
377
+ """
378
+ pass
379
+
361
380
  async def restart_task(self, task_progression: TaskProgression, task: MqttTask):
362
381
  """
363
382
  Restart a task
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: manta_node
3
- Version: 0.5b0.dev91
3
+ Version: 0.5b0.dev117
4
4
  Summary: Node side software of the Manta platform. It is responsible for the communication with the Manta Core and the execution of the tasks.
5
5
  Author-email: Hugo Miralles <hugomiralles@manta-tech.io>, Matthew Thompson <matthew.thompson@manta-tech.io>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -1,7 +1,7 @@
1
1
  manta_node/__init__.py,sha256=bgGWXH3yQVhNf0v1ZGUBAy5Y51fqb0Y-EaLnocbcino,153
2
2
  manta_node/__main__.py,sha256=0VzbY1KfezaARZDuCBc7Th1GO_-BnKTK8eDEXhgP5J4,1731
3
3
  manta_node/node_orchestrator.py,sha256=0VMBsZSMQiIe9L-Bd7Jn4avNpNyDCMa1kyPSTgRiOes,17336
4
- manta_node/task_manager.py,sha256=B_h6Mc7to8CTzLLgqrINgEALTYWX1Aek3Tio34ICXag,19887
4
+ manta_node/task_manager.py,sha256=cusrPrctHC4NY6Ryb_hu9BCEVi2DUceu99H-56ztVTA,20523
5
5
  manta_node/tasks.py,sha256=W__XjU17AX8VqOtgy8HRBmxWtVufTkFvri8s8Y3R394,6721
6
6
  manta_node/utils.py,sha256=gkYRB7fylk1jy1sm7nKU7o8eadGxn0AX4IjCHT6Wzuw,1179
7
7
  manta_node/cli/__init__.py,sha256=arzO3mXDJ-E2yeeytbZfN7mCswwxdQ43_QjyeL2E5yE,134
@@ -34,10 +34,10 @@ manta_node/infrastructure/metrics/__init__.py,sha256=ODGAj9s63F5jba2c3dQRMfNK6I4
34
34
  manta_node/infrastructure/metrics/collector.py,sha256=nhFPAbjA04ye3UaGN95_zdpf9hLs2lrMXwOwKlFKYsI,3070
35
35
  manta_node/infrastructure/metrics/metrics_collector.py,sha256=jhEocAPWS6xgbNvcBBiEB9cAN_9hEGssZJXoysQzWOk,10518
36
36
  manta_node/infrastructure/mqtt/__init__.py,sha256=riXo1sXgO4YV6DHCmWRqK61BAmuGLdHiVdQFunAJadA,140
37
- manta_node/infrastructure/mqtt/command_handler.py,sha256=bDH8k3Vkbbb7q3P68VamhMbtLJ4vSSivqZP3IfMBqIQ,11833
38
- manta_node-0.5b0.dev91.dist-info/licenses/LICENSE,sha256=P5HojupjKoAusNtHqWOvO-YFolwLFWQ7Uq0XVKK9nFQ,35291
39
- manta_node-0.5b0.dev91.dist-info/METADATA,sha256=A0_n5jnU92mC1l6gTncaA5r-PGfkTC6W-rjDogDBXdA,44653
40
- manta_node-0.5b0.dev91.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
41
- manta_node-0.5b0.dev91.dist-info/entry_points.txt,sha256=C_zcw6mdmztNxVUZGLZ83UH5TwcSuqb2C-rB4e2CQoc,56
42
- manta_node-0.5b0.dev91.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
43
- manta_node-0.5b0.dev91.dist-info/RECORD,,
37
+ manta_node/infrastructure/mqtt/command_handler.py,sha256=Q-QYtjJVD2UByEzbbVpwRt67BAws81XaJTZlkNw7nY0,13888
38
+ manta_node-0.5b0.dev117.dist-info/licenses/LICENSE,sha256=P5HojupjKoAusNtHqWOvO-YFolwLFWQ7Uq0XVKK9nFQ,35291
39
+ manta_node-0.5b0.dev117.dist-info/METADATA,sha256=L1BilnSrL844ItSAKHZNZmwCaUXKnIiLC7V4w2cl3r8,44654
40
+ manta_node-0.5b0.dev117.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
41
+ manta_node-0.5b0.dev117.dist-info/entry_points.txt,sha256=C_zcw6mdmztNxVUZGLZ83UH5TwcSuqb2C-rB4e2CQoc,56
42
+ manta_node-0.5b0.dev117.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
43
+ manta_node-0.5b0.dev117.dist-info/RECORD,,