Repository: MatterMiners/lapis

Issue:
Jobs can run without a drone being assigned
Apparently jobs can run although not being properly assigned to a drone. We should take care that this cannot happen.

See:

> This happens in the unit tests... Same with the fileprovider in 
> https://github.com/tfesenbecker/lapis/blob/53ebec5ea004e4f031a0f70102276fe0102d62e4/lapis/job.py#L96

_Originally posted by @tfesenbecker in https://github.com/MatterMiners/lapis/pull/53_

Discussion:

Patch:
diff --git a/lapis/drone.py b/lapis/drone.py
index 7126242..0e6845a 100644
--- a/lapis/drone.py
+++ b/lapis/drone.py
@@ -127,7 +127,7 @@ class Drone(interfaces.Pool):
 
             self._utilisation = self._allocation = None
 
-            job_execution = scope.do(job.run())
+            job_execution = scope.do(job.run(self))
             self.jobs += 1
             try:
                 async with self.resources.claim(
@@ -154,7 +154,6 @@ class Drone(interfaces.Pool):
                 await instant
                 job_execution.cancel()
             self.jobs -= 1
-            job.drone = None
             await self.scheduler.job_finished(job)
             self._utilisation = self._allocation = None
             self.scheduler.update_drone(self)
diff --git a/lapis/job.py b/lapis/job.py
index 934ceee..c4627e0 100644
--- a/lapis/job.py
+++ b/lapis/job.py
@@ -91,18 +91,23 @@ class Job(object):
             return self.in_queue_until - self.in_queue_since
         return float("Inf")
 
-    async def run(self):
+    async def run(self, drone: "Drone"):
+        assert drone, "Jobs cannot run without a drone being assigned"
+        self.drone = drone
         self.in_queue_until = time.now
         self._success = None
         await sampling_required.put(self)
         try:
             await (time + self.walltime)
         except CancelTask:
+            self.drone = None
             self._success = False
         except BaseException:
+            self.drone = None
             self._success = False
             raise
         else:
+            self.drone = None
             self._success = True
         await sampling_required.put(self)
 


Test patch:
diff --git a/lapis_tests/__init__.py b/lapis_tests/__init__.py
index d0c54e8..722b3a2 100644
--- a/lapis_tests/__init__.py
+++ b/lapis_tests/__init__.py
@@ -43,3 +43,7 @@ class DummyScheduler:
     @staticmethod
     def update_drone(drone: Drone):
         pass
+
+
+class DummyDrone:
+    pass
diff --git a/lapis_tests/test_job.py b/lapis_tests/test_job.py
index 3c75916..181bb1a 100644
--- a/lapis_tests/test_job.py
+++ b/lapis_tests/test_job.py
@@ -3,7 +3,7 @@ from usim import Scope, time
 
 from lapis.drone import Drone
 from lapis.job import Job
-from lapis_tests import via_usim, DummyScheduler
+from lapis_tests import via_usim, DummyScheduler, DummyDrone
 
 
 class TestJob(object):
@@ -27,10 +27,11 @@ class TestJob(object):
 
     @via_usim
     async def test_run_job(self):
+        drone = DummyDrone()
         job = Job(resources={"walltime": 50}, used_resources={"walltime": 10})
         assert float("inf") == job.waiting_time
         async with Scope() as scope:
-            scope.do(job.run())
+            scope.do(job.run(drone))
         assert 10 == time
         assert 0 == job.waiting_time
         assert job.successful