DIRAC 9.0.8__py3-none-any.whl → 9.0.9__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.
@@ -1,7 +1,7 @@
1
1
  """
2
2
  DIRAC Wrapper to execute python and system commands with a wrapper, that might
3
3
  set a timeout.
4
- 3 FUNCTIONS are provided:
4
+ 3 functions are provided:
5
5
 
6
6
  - shellCall( iTimeOut, cmdSeq, callbackFunction = None, env = None ):
7
7
  it uses subprocess.Popen class with "shell = True".
@@ -26,6 +26,7 @@ set a timeout.
26
26
  should be used to wrap third party python functions
27
27
 
28
28
  """
29
+
29
30
  import os
30
31
  import selectors
31
32
  import signal
@@ -161,7 +162,6 @@ class Subprocess:
161
162
 
162
163
  self.child = None
163
164
  self.childPID = 0
164
- self.childKilled = False
165
165
  self.callback = None
166
166
  self.bufferList = []
167
167
  self.cmdSeq = []
@@ -193,7 +193,7 @@ class Subprocess:
193
193
  f"First and last data in buffer: \n{dataString[:100]} \n....\n {dataString[-100:]} ",
194
194
  )
195
195
  retDict = S_ERROR(
196
- "Reached maximum allowed length (%d bytes) " "for called function return value" % self.bufferLimit
196
+ "Reached maximum allowed length (%d bytes) for called function return value" % self.bufferLimit
197
197
  )
198
198
  retDict["Value"] = dataString
199
199
  return retDict
@@ -241,60 +241,71 @@ class Subprocess:
241
241
  events = sel.select(timeout=timeout or self.timeout or None)
242
242
  return [key.fileobj for key, event in events if event & selectors.EVENT_READ]
243
243
 
244
- def __killPid(self, pid, sig=9):
245
- """send signal :sig: to process :pid:
246
-
247
- :param int pid: process id
248
- :param int sig: signal to send, default 9 (SIGKILL)
249
- """
244
+ def __terminateProcess(self, process):
245
+ """Tries to terminate a process with SIGTERM. Returns a (gone, alive) tuple"""
246
+ self.log.verbose(f"Sending SIGTERM signal to PID {process.pid}")
250
247
  try:
251
- os.kill(pid, sig)
252
- except Exception as x:
253
- if str(x) != "[Errno 3] No such process":
254
- self.log.exception("Exception while killing timed out process")
255
- raise x
256
-
257
- def __poll(self, pid):
258
- """wait for :pid:"""
248
+ process.terminate()
249
+ except psutil.NoSuchProcess:
250
+ return ([], [])
251
+ return psutil.wait_procs([process], timeout=60)
252
+
253
+ def __poll(self, process):
254
+ """Non-blocking check of whether process `pid` is still alive.
255
+ Returns:
256
+ - (0, 0) if process is still running (like os.waitpid(pid, os.WNOHANG))
257
+ - (pid, exitcode) if process has terminated
258
+ - None if process info cannot be retrieved
259
+ """
259
260
  try:
260
- return os.waitpid(pid, os.WNOHANG)
261
- except os.error:
262
- if self.childKilled:
263
- return False
261
+ exitcode = process.wait(timeout=0)
262
+ return (process.pid, exitcode) # exited
263
+ except psutil.TimeoutExpired:
264
+ return (0, 0) # still running
265
+ except psutil.NoSuchProcess:
264
266
  return None
265
267
 
266
268
  def killChild(self, recursive=True):
267
- """kill child process
268
-
269
- :param boolean recursive: flag to kill all descendants
269
+ """Kills a process tree (including children) with signal SIGTERM. If that fails, escalate to SIGKILL
270
+ returns (gone, alive) tuple.
270
271
  """
271
- pgid = os.getpgid(self.childPID)
272
- if pgid != os.getpgrp():
273
- try:
274
- # Child is in its own group: kill the group
275
- os.killpg(pgid, signal.SIGTERM)
276
- except OSError:
277
- # Process is already dead
278
- pass
279
- else:
280
- # No separate group: walk the tree
281
- parent = psutil.Process(self.childPID)
282
- procs = parent.children(recursive=recursive)
283
- procs.append(parent)
284
- for p in procs:
272
+
273
+ self.log.info(f"Killing childPID {self.childPID}")
274
+
275
+ gone, alive = [], []
276
+ try:
277
+ child_process = psutil.Process(self.childPID)
278
+ except psutil.NoSuchProcess:
279
+ self.log.warn(f"Child PID {self.childPID} no longer exists")
280
+ return (gone, alive)
281
+
282
+ if recursive:
283
+ # grandchildren
284
+ children = child_process.children(recursive=True)
285
+ self.log.info(f"Sending kill signal to {len(children)} children PIDs")
286
+ for p in children:
285
287
  try:
286
288
  p.terminate()
287
289
  except psutil.NoSuchProcess:
288
- pass
289
- _gone, alive = psutil.wait_procs(procs, timeout=10)
290
- # Escalate any survivors
290
+ continue
291
+ g, a = psutil.wait_procs(children, timeout=60)
292
+ gone.extend(g)
293
+ alive.extend(a)
294
+
295
+ # now killing the child_process
296
+ g, a = self.__terminateProcess(child_process)
297
+ gone.extend(g)
298
+ alive.extend(a)
299
+
300
+ # if there's something still alive, use SIGKILL
301
+ if alive:
291
302
  for p in alive:
292
303
  try:
293
304
  p.kill()
294
305
  except psutil.NoSuchProcess:
295
306
  pass
296
307
 
297
- self.childKilled = True
308
+ return psutil.wait_procs(alive, timeout=60)
298
309
 
299
310
  def pythonCall(self, function, *stArgs, **stKeyArgs):
300
311
  """call python function :function: with :stArgs: and :stKeyArgs:"""
@@ -309,8 +320,6 @@ class Subprocess:
309
320
  if pid == 0:
310
321
  os.close(readFD)
311
322
  self.__executePythonFunction(function, writeFD, *stArgs, **stKeyArgs)
312
- # FIXME: the close it is done at __executePythonFunction, do we need it here?
313
- os.close(writeFD)
314
323
  else:
315
324
  os.close(writeFD)
316
325
  readSeq = self.__selectFD([readFD])
@@ -319,14 +328,13 @@ class Subprocess:
319
328
  try:
320
329
  if len(readSeq) == 0:
321
330
  self.log.debug("Timeout limit reached for pythonCall", function.__name__)
322
- self.__killPid(pid)
323
-
324
- # HACK to avoid python bug
325
- # self.wait()
326
- retries = 10000
327
- while os.waitpid(pid, 0) == -1 and retries > 0:
328
- time.sleep(0.001)
329
- retries -= 1
331
+ gone, alive = self.__terminateProcess(psutil.Process(pid))
332
+ if alive:
333
+ for p in alive:
334
+ try:
335
+ p.kill()
336
+ except psutil.NoSuchProcess:
337
+ continue
330
338
 
331
339
  return S_ERROR('%d seconds timeout for "%s" call' % (self.timeout, function.__name__))
332
340
  elif readSeq[0] == readFD:
@@ -400,7 +408,7 @@ class Subprocess:
400
408
  if len(dataString) + baseLength > self.bufferLimit:
401
409
  self.log.error("Maximum output buffer length reached")
402
410
  retDict = S_ERROR(
403
- "Reached maximum allowed length (%d bytes) for called " "function return value" % self.bufferLimit
411
+ "Reached maximum allowed length (%d bytes) for called function return value" % self.bufferLimit
404
412
  )
405
413
  retDict["Value"] = dataString
406
414
  return retDict
@@ -446,6 +454,7 @@ class Subprocess:
446
454
  start_new_session=start_new_session,
447
455
  )
448
456
  self.childPID = self.child.pid
457
+ child_process = psutil.Process(self.childPID)
449
458
  except OSError as v:
450
459
  retDict = S_ERROR(repr(v))
451
460
  retDict["Value"] = (-1, "", str(v))
@@ -464,9 +473,9 @@ class Subprocess:
464
473
  self.bufferList = [["", 0], ["", 0]]
465
474
  initialTime = time.time()
466
475
 
467
- exitStatus = self.__poll(self.child.pid)
476
+ exitStatus = self.__poll(child_process)
468
477
 
469
- while (0, 0) == exitStatus or exitStatus is None:
478
+ while (0, 0) == exitStatus: # This means that the process is still alive
470
479
  retDict = self.__readFromCommand()
471
480
  if not retDict["OK"]:
472
481
  return retDict
@@ -478,14 +487,14 @@ class Subprocess:
478
487
  1, "Timeout (%d seconds) for '%s' call" % (self.timeout, cmdSeq)
479
488
  )
480
489
  time.sleep(0.01)
481
- exitStatus = self.__poll(self.child.pid)
490
+ exitStatus = self.__poll(child_process)
482
491
 
483
492
  self.__readFromCommand()
484
493
 
485
494
  if exitStatus:
486
495
  exitStatus = exitStatus[1]
487
496
 
488
- if exitStatus >= 256:
497
+ if exitStatus and exitStatus >= 256:
489
498
  exitStatus = int(exitStatus / 256)
490
499
  return S_OK((exitStatus, self.bufferList[0][0], self.bufferList[1][0]))
491
500
  finally:
@@ -3,23 +3,25 @@
3
3
  # Date: 2012/12/11 18:04:25
4
4
  ########################################################################
5
5
 
6
- """ :mod: SubprocessTests
7
- =======================
6
+ """:mod: SubprocessTests
7
+ =======================
8
8
 
9
- .. module: SubprocessTests
10
- :synopsis: unittest for Subprocess module
11
- .. moduleauthor:: Krzysztof.Ciba@NOSPAMgmail.com
9
+ .. module: SubprocessTests
10
+ :synopsis: unittest for Subprocess module
11
+ .. moduleauthor:: Krzysztof.Ciba@NOSPAMgmail.com
12
12
 
13
- unittest for Subprocess module
13
+ unittest for Subprocess module
14
14
  """
15
- import time
15
+
16
16
  import platform
17
+ import time
17
18
  from os.path import dirname, join
18
19
  from subprocess import Popen
19
20
 
21
+ import psutil
20
22
  import pytest
21
23
 
22
- from DIRAC.Core.Utilities.Subprocess import systemCall, shellCall, pythonCall, getChildrenPIDs, Subprocess
24
+ from DIRAC.Core.Utilities.Subprocess import Subprocess, getChildrenPIDs, pythonCall, shellCall, systemCall
23
25
 
24
26
  # Mark this entire module as slow
25
27
  pytestmark = pytest.mark.slow
@@ -72,3 +74,51 @@ def test_decodingCommandOutput():
72
74
  retVal = sp.systemCall(r"""python -c 'import os; os.fdopen(2, "wb").write(b"\xdf")'""", shell=True)
73
75
  assert retVal["OK"]
74
76
  assert retVal["Value"] == (0, "", "\ufffd")
77
+
78
+
79
+ @pytest.fixture
80
+ def subprocess_instance():
81
+ """Provides a Subprocess instance for testing."""
82
+ subp = Subprocess()
83
+ return subp
84
+
85
+
86
+ @pytest.fixture
87
+ def dummy_child():
88
+ """Spawn a dummy process tree: parent -> child."""
89
+ # Start a shell that sleeps, with a subprocess child
90
+ parent = Popen(["bash", "-c", "sleep 10 & wait"])
91
+ time.sleep(0.2) # give it a moment to start
92
+ yield parent
93
+ # Ensure cleanup
94
+ try:
95
+ parent.terminate()
96
+ parent.wait(timeout=1)
97
+ except Exception:
98
+ pass
99
+
100
+
101
+ def test_kill_child_process_tree(subprocess_instance, dummy_child):
102
+ """Test that killChild kills both parent and its children."""
103
+ subprocess_instance.childPID = dummy_child.pid
104
+ parent_proc = psutil.Process(subprocess_instance.childPID)
105
+
106
+ # Sanity check: parent should exist
107
+ assert parent_proc.is_running()
108
+
109
+ # It should have at least one sleeping child
110
+ children = parent_proc.children(recursive=True)
111
+ assert children, "Expected dummy process to have at least one child"
112
+
113
+ # Kill the tree
114
+ gone, alive = subprocess_instance.killChild(recursive=True)
115
+
116
+ # Verify the parent and children are terminated
117
+ for p in gone:
118
+ assert not p.is_running(), f"Process {p.pid} still alive"
119
+ for p in alive:
120
+ assert not p.is_running(), f"Process {p.pid} still alive"
121
+
122
+ # Verify parent is gone
123
+ with pytest.raises(psutil.NoSuchProcess):
124
+ psutil.Process(subprocess_instance.childPID)
@@ -1,22 +1,22 @@
1
- """ The Watchdog class is used by the Job Wrapper to resolve and monitor
2
- the system resource consumption. The Watchdog can determine if
3
- a running job is stalled and indicate this to the Job Wrapper.
4
- Furthermore, the Watchdog will identify when the Job CPU limit has been
5
- exceeded and fail jobs meaningfully.
6
-
7
- Information is returned to the WMS via the heart-beat mechanism. This
8
- also interprets control signals from the WMS e.g. to kill a running
9
- job.
10
-
11
- - Still to implement:
12
- - CPU normalization for correct comparison with job limit
1
+ """The Watchdog class is used by the Job Wrapper to resolve and monitor
2
+ the system resource consumption. The Watchdog can determine if
3
+ a running job is stalled and indicate this to the Job Wrapper.
4
+ Furthermore, the Watchdog will identify when the Job CPU limit has been
5
+ exceeded and fail jobs meaningfully.
6
+
7
+ Information is returned to the WMS via the heart-beat mechanism. This
8
+ also interprets control signals from the WMS e.g. to kill a running
9
+ job.
10
+
11
+ - Still to implement:
12
+ - CPU normalization for correct comparison with job limit
13
13
  """
14
+
14
15
  import datetime
15
16
  import errno
16
17
  import getpass
17
18
  import math
18
19
  import os
19
- import signal
20
20
  import socket
21
21
  import time
22
22
  from pathlib import Path
@@ -32,28 +32,6 @@ from DIRAC.WorkloadManagementSystem.Client import JobMinorStatus
32
32
  from DIRAC.WorkloadManagementSystem.Client.JobStateUpdateClient import JobStateUpdateClient
33
33
 
34
34
 
35
- def kill_proc_tree(pid, sig=signal.SIGTERM, includeParent=True):
36
- """Kill a process tree (including grandchildren) with signal
37
- "sig" and return a (gone, still_alive) tuple.
38
- called as soon as a child terminates.
39
-
40
- Taken from https://psutil.readthedocs.io/en/latest/index.html#kill-process-tree
41
- """
42
- assert pid != os.getpid(), "won't kill myself"
43
- parent = psutil.Process(pid)
44
- children = parent.children(recursive=True)
45
- if includeParent:
46
- children.append(parent)
47
- for p in children:
48
- try:
49
- p.send_signal(sig)
50
- except psutil.NoSuchProcess:
51
- pass
52
- _gone, alive = psutil.wait_procs(children, timeout=10)
53
- for p in alive:
54
- p.kill()
55
-
56
-
57
35
  class Watchdog:
58
36
  #############################################################################
59
37
  def __init__(self, pid, exeThread, spObject, jobCPUTime, memoryLimit=0, processors=1, jobArgs={}):
@@ -212,7 +190,7 @@ class Watchdog:
212
190
  if self.littleTimeLeftCount == 0 and self.__timeLeft() == -1:
213
191
  self.checkError = JobMinorStatus.JOB_EXCEEDED_CPU
214
192
  self.log.error(self.checkError, self.timeLeft)
215
- self.__killRunningThread()
193
+ self.spObject.killChild()
216
194
  return S_OK()
217
195
 
218
196
  self.littleTimeLeftCount -= 1
@@ -321,7 +299,7 @@ class Watchdog:
321
299
 
322
300
  self.log.info("=================END=================")
323
301
 
324
- self.__killRunningThread()
302
+ self.spObject.killChild()
325
303
  return S_OK()
326
304
 
327
305
  recentStdOut = "None"
@@ -408,7 +386,7 @@ class Watchdog:
408
386
  if "Kill" in signalDict:
409
387
  self.log.info("Received Kill signal, stopping job via control signal")
410
388
  self.checkError = JobMinorStatus.RECEIVED_KILL_SIGNAL
411
- self.__killRunningThread()
389
+ self.spObject.killChild()
412
390
  else:
413
391
  self.log.info("The following control signal was sent but not understood by the watchdog:")
414
392
  self.log.info(signalDict)
@@ -862,13 +840,6 @@ class Watchdog:
862
840
 
863
841
  return result
864
842
 
865
- #############################################################################
866
- def __killRunningThread(self):
867
- """Will kill the running thread process and any child processes."""
868
- self.log.info("Sending kill signal to application PID", self.spObject.getChildPID())
869
- self.spObject.killChild()
870
- return S_OK("Thread killed")
871
-
872
843
  #############################################################################
873
844
  def __sendSignOfLife(self, jobID, heartBeatDict, staticParamDict):
874
845
  """Sends sign of life 'heartbeat' signal and triggers control signal
@@ -1,5 +1,5 @@
1
- """ Test class for JobWrapper
2
- """
1
+ """Test class for JobWrapper"""
2
+
3
3
  import os
4
4
  import shutil
5
5
  import tempfile
@@ -314,8 +314,7 @@ def test_processKilledSubprocess(mocker):
314
314
  result = jw.process("sleep 20", {})
315
315
 
316
316
  assert result["OK"]
317
- assert result["Value"]["payloadStatus"] == 15 # SIGTERM
318
- assert not result["Value"]["payloadOutput"]
317
+ assert result["Value"]["payloadStatus"] is None
319
318
  assert not result["Value"]["payloadExecutorError"]
320
319
  assert result["Value"]["watchdogError"] == "Job is stalled!" # Error message from the watchdog
321
320
 
@@ -218,7 +218,7 @@ class PilotCStoJSONSynchronizer:
218
218
 
219
219
  preferredURLPatterns = gConfigurationData.extractOptionFromCFG("/DIRAC/PreferredURLPatterns")
220
220
  if preferredURLPatterns:
221
- pilotDict["PreferredURLPatterns"] = preferredURLPatterns
221
+ pilotDict["PreferredURLPatterns"] = preferredURLPatterns.replace(" ", "").split(",")
222
222
 
223
223
  self.log.debug("Got pilotDict", str(pilotDict))
224
224
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DIRAC
3
- Version: 9.0.8
3
+ Version: 9.0.9
4
4
  Summary: DIRAC is an interware, meaning a software framework for distributed computing.
5
5
  Home-page: https://github.com/DIRACGrid/DIRAC/
6
6
  License: GPL-3.0-only
@@ -19,7 +19,7 @@ Requires-Dist: cachetools
19
19
  Requires-Dist: certifi
20
20
  Requires-Dist: cwltool
21
21
  Requires-Dist: diraccfg
22
- Requires-Dist: DIRACCommon==v9.0.8
22
+ Requires-Dist: DIRACCommon==v9.0.9
23
23
  Requires-Dist: diracx-client>=v0.0.1
24
24
  Requires-Dist: diracx-core>=v0.0.1
25
25
  Requires-Dist: diracx-cli>=v0.0.1
@@ -259,7 +259,7 @@ DIRAC/Core/Utilities/ReturnValues.py,sha256=EHtnn1I3Lvq6RO9tTVZMlf19a8QGX1B6Qa7l
259
259
  DIRAC/Core/Utilities/Shifter.py,sha256=EkJGPR_zbL0SS3E2_cq1-MjkTH91ZaFJlh-tQ2PxDGk,2571
260
260
  DIRAC/Core/Utilities/SiteSEMapping.py,sha256=Uynx_evXQPmfbMKH-TfqzLq4e_7APVrP6gC20cJEBYs,5683
261
261
  DIRAC/Core/Utilities/StateMachine.py,sha256=e1rc0X6J8Cf_W9HlLnLgroLkdrksUQUxdkH_qSoZVoA,769
262
- DIRAC/Core/Utilities/Subprocess.py,sha256=FcdEKKEySh_62S8VLjWp_HnuFYkKAjqsS8FLpPMS1b8,22756
262
+ DIRAC/Core/Utilities/Subprocess.py,sha256=_lNp7ncg6BYC2TLvkcqACelZSsb0_5oyM2Jq-T5YbFc,23315
263
263
  DIRAC/Core/Utilities/ThreadPool.py,sha256=tETtgUdW1dlq_7xp_nQUTxNC87jAkrNnwvuDpGS3ffE,11484
264
264
  DIRAC/Core/Utilities/ThreadSafe.py,sha256=EAxEMqEuYlzDvAaupW3fi2MY-3kGZJ0zbK9i7JhEmt4,1117
265
265
  DIRAC/Core/Utilities/ThreadScheduler.py,sha256=IzyS59NF0ZhQwGYLpZ-yMX8_6kXFSGL9FQapylnoPLE,6212
@@ -304,7 +304,7 @@ DIRAC/Core/Utilities/test/Test_Pfn.py,sha256=XWTXejQf_TnicaPbQ4ZyDsl1KXDpq3IMNFl
304
304
  DIRAC/Core/Utilities/test/Test_ProcessPool.py,sha256=NnfHNkhTOgADk-P9ds_ADuvcSNlK_XdoibRkk13r_NE,10890
305
305
  DIRAC/Core/Utilities/test/Test_Profiler.py,sha256=8QRRXm-PwJpjLGwAOJxnrpnnMbmVzXhJ1ZcQ9gDBX5c,4215
306
306
  DIRAC/Core/Utilities/test/Test_ReturnValues.py,sha256=w6Jz-Vblgu8RDXPzVi6BZjuFXcSmW-Zb0mcBgW1RWOw,1926
307
- DIRAC/Core/Utilities/test/Test_Subprocess.py,sha256=nAiVF5oMnle-4E8ijlWF-ilBfgN2VGkkFQQSKhIKN2o,2141
307
+ DIRAC/Core/Utilities/test/Test_Subprocess.py,sha256=f9THTKnVdGcNwpZ0PtfCQz0UAtNmF06126-F0mUAxWQ,3605
308
308
  DIRAC/Core/Utilities/test/Test_entrypoints.py,sha256=z_3f7m59v3W6ZsqgeCFbJoBnMY-cKR_blKbPqcV7528,413
309
309
  DIRAC/Core/Utilities/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
310
310
  DIRAC/Core/Workflow/Module.py,sha256=XF_iDyJ1wIwUesHLfDGz9BywHgEuOwqzC2f7fY3E7r4,12631
@@ -1209,9 +1209,9 @@ DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py,sha256=s_YHg_PsTGVHBLrS1
1209
1209
  DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapperOfflineTemplate.py,sha256=wem5VDN9XiC7szAzdsbgHUxpIOQB2Hj36DIVMoV9px8,2490
1210
1210
  DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapperTemplate.py,sha256=4QgcFPMLRaTagP9e_Vvsla8pFH8HdewklHfS-gyS4-g,3313
1211
1211
  DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapperUtilities.py,sha256=5w_4PMnaHhuexChADDvt1L9Ih1PstdUuYWObnlv9Dto,10072
1212
- DIRAC/WorkloadManagementSystem/JobWrapper/Watchdog.py,sha256=wGpIdnyVzI5T9agxNp94gmPZPceXWREaJiEtZg1lAzk,39997
1212
+ DIRAC/WorkloadManagementSystem/JobWrapper/Watchdog.py,sha256=UWVDC_tWzlE9i5PsLycrRda4j3hWjaK1RnQVOvydg6U,38857
1213
1213
  DIRAC/WorkloadManagementSystem/JobWrapper/__init__.py,sha256=e9Oa_ddNLweR3Lp_HOMK6WqqCWWj2SLPxF5UH4F19ic,61
1214
- DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py,sha256=R9onrrnfyc1v4USt2nnvrYHFo5tKbpLjhMkXl4n0y2Y,39177
1214
+ DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py,sha256=AhevZTkZfFQV9XN5UTWFmoMe9GvwN_Caorto1o_YoU8,39119
1215
1215
  DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapperTemplate.py,sha256=dC_SvC5Rlchlj2NvBfN7FH1ioXgC8bf9U8BQnEL5GYg,21982
1216
1216
  DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_Watchdog.py,sha256=a-QJ1E1ZcWObhOVgxZYD_nYjseCWsbjT0KxjZDNWyAQ,882
1217
1217
  DIRAC/WorkloadManagementSystem/JobWrapper/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1242,7 +1242,7 @@ DIRAC/WorkloadManagementSystem/Utilities/JobModel.py,sha256=jN9sFbzMZo9tab6Kp7Oe
1242
1242
  DIRAC/WorkloadManagementSystem/Utilities/JobParameters.py,sha256=JW3AAEtBJn1gIO_rm2Ft5qqjfLteIo3HpQtGNZBfhxE,8365
1243
1243
  DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py,sha256=WtGJzC7fHvydANh8JH6e1Kk_jebrCMPr2c5cw3ufjm8,7826
1244
1244
  DIRAC/WorkloadManagementSystem/Utilities/ParametricJob.py,sha256=FNUsGhvsFVrtmA7r8G-sd4QTMeBkqG1sdtwiBUKQyd0,605
1245
- DIRAC/WorkloadManagementSystem/Utilities/PilotCStoJSONSynchronizer.py,sha256=Ezpfd90dV_j6fOn25v5gr-wNWh1nMpCOQZfPmGDHJD4,12448
1245
+ DIRAC/WorkloadManagementSystem/Utilities/PilotCStoJSONSynchronizer.py,sha256=ZQk2tD40986awO9pae1zmdPEWlnMJt4m61Z_RU3LWl8,12476
1246
1246
  DIRAC/WorkloadManagementSystem/Utilities/PilotWrapper.py,sha256=VcvQTpeyTbVYqSsPQDyAt37N2CaEAnIuvbR6yk4kYk8,15465
1247
1247
  DIRAC/WorkloadManagementSystem/Utilities/QueueUtilities.py,sha256=J5-n_lvWbW_TRjrlqp8hx1SHEaXDW2Dxp3R1hBBrWnE,12082
1248
1248
  DIRAC/WorkloadManagementSystem/Utilities/RemoteRunner.py,sha256=7FcEtlYSJMzdbLIFBUKD-j_wqRHya-ISqk8w-JRy3kw,12159
@@ -1296,9 +1296,9 @@ DIRAC/tests/Workflow/Integration/exe-script.py,sha256=B_slYdTocEzqfQLRhwuPiLyYUn
1296
1296
  DIRAC/tests/Workflow/Integration/helloWorld.py,sha256=tBgEHH3ZF7ZiTS57gtmm3DW-Qxgm_57HWHpM-Y8XSws,205
1297
1297
  DIRAC/tests/Workflow/Regression/helloWorld.py,sha256=69eCgFuVSYo-mK3Dj2dw1c6g86sF5FksKCf8V2aGVoM,509
1298
1298
  DIRAC/tests/Workflow/Regression/helloWorld.xml,sha256=xwydIcFTAHIX-YPfQfyxuQ7hzvIO3IhR3UAF7ORgkGg,5310
1299
- dirac-9.0.8.dist-info/licenses/LICENSE,sha256=uyr4oV6jmjUeepXZPPjkJRwa5q5MrI7jqJz5sVXNblQ,32452
1300
- dirac-9.0.8.dist-info/METADATA,sha256=gpKRR8YAjDBxrxogcUUGPNXojPku4FhNUVqBVSPRNWg,10016
1301
- dirac-9.0.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1302
- dirac-9.0.8.dist-info/entry_points.txt,sha256=hupzIL8aVmjK3nn7RLKdhcaiPmLOiD3Kulh3CSDHKmw,16492
1303
- dirac-9.0.8.dist-info/top_level.txt,sha256=RISrnN9kb_mPqmVu8_o4jF-DSX8-h6AcgfkO9cgfkHA,6
1304
- dirac-9.0.8.dist-info/RECORD,,
1299
+ dirac-9.0.9.dist-info/licenses/LICENSE,sha256=uyr4oV6jmjUeepXZPPjkJRwa5q5MrI7jqJz5sVXNblQ,32452
1300
+ dirac-9.0.9.dist-info/METADATA,sha256=UjEl56_6P3co7mVc4nFt9FsB51VuvNFA-MkK1kHsQGk,10016
1301
+ dirac-9.0.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1302
+ dirac-9.0.9.dist-info/entry_points.txt,sha256=hupzIL8aVmjK3nn7RLKdhcaiPmLOiD3Kulh3CSDHKmw,16492
1303
+ dirac-9.0.9.dist-info/top_level.txt,sha256=RISrnN9kb_mPqmVu8_o4jF-DSX8-h6AcgfkO9cgfkHA,6
1304
+ dirac-9.0.9.dist-info/RECORD,,
File without changes