Skip to content

Commit d948fc5

Browse files
committed
Fix system.multicall() broken by faster start/stop patch
In past versions, startProcess() and stopProcess() would always return a callback. 50d1857 changed this so they may return either a callback or a bool, but the code that handles system.multicall() was not updated. If multicall was used with stopProcess() followed by startProcess(), it would try to start the process before it had finished stopping. This broke the restart process link on the web interface.
1 parent 4d34d90 commit d948fc5

File tree

4 files changed

+216
-135
lines changed

4 files changed

+216
-135
lines changed

supervisor/tests/test_rpcinterfaces.py

+13-23
Original file line numberDiff line numberDiff line change
@@ -2163,44 +2163,34 @@ def test_allMethodDocs(self):
21632163

21642164
def test_multicall_simplevals(self):
21652165
interface = self._makeOne()
2166-
callback = interface.multicall([
2166+
results = interface.multicall([
21672167
{'methodName':'system.methodHelp', 'params':['system.methodHelp']},
21682168
{'methodName':'system.listMethods', 'params':[]},
21692169
])
2170-
from supervisor import http
2171-
result = http.NOT_DONE_YET
2172-
while result is http.NOT_DONE_YET:
2173-
result = callback()
2174-
self.assertEqual(result[0], interface.methodHelp('system.methodHelp'))
2175-
self.assertEqual(result[1], interface.listMethods())
2170+
self.assertEqual(results[0], interface.methodHelp('system.methodHelp'))
2171+
self.assertEqual(results[1], interface.listMethods())
21762172

21772173
def test_multicall_recursion_guard(self):
21782174
from supervisor import xmlrpc
21792175
interface = self._makeOne()
2180-
callback = interface.multicall([
2176+
results = interface.multicall([
21812177
{'methodName': 'system.multicall', 'params': []},
21822178
])
21832179

2184-
from supervisor import http
2185-
result = http.NOT_DONE_YET
2186-
while result is http.NOT_DONE_YET:
2187-
result = callback()
2188-
2189-
code = xmlrpc.Faults.INCORRECT_PARAMETERS
2190-
desc = xmlrpc.getFaultDescription(code)
2191-
recursion_fault = {'faultCode': code, 'faultString': desc}
2192-
2193-
self.assertEqual(result, [recursion_fault])
2180+
e = xmlrpc.RPCError(xmlrpc.Faults.INCORRECT_PARAMETERS,
2181+
'Recursive system.multicall forbidden')
2182+
recursion_fault = {'faultCode': e.code, 'faultString': e.text}
2183+
self.assertEqual(results, [recursion_fault])
21942184

21952185
def test_multicall_nested_callback(self):
2186+
from supervisor import http
21962187
interface = self._makeOne()
21972188
callback = interface.multicall([
21982189
{'methodName':'supervisor.stopAllProcesses'}])
2199-
from supervisor import http
2200-
result = http.NOT_DONE_YET
2201-
while result is http.NOT_DONE_YET:
2202-
result = callback()
2203-
self.assertEqual(result[0], [])
2190+
results = http.NOT_DONE_YET
2191+
while results is http.NOT_DONE_YET:
2192+
results = callback()
2193+
self.assertEqual(results[0], [])
22042194

22052195
def test_methodHelp(self):
22062196
from supervisor import xmlrpc

supervisor/tests/test_xmlrpc.py

+121-55
Original file line numberDiff line numberDiff line change
@@ -569,86 +569,152 @@ def foo(self):
569569
inst = self._makeOne([('ns1', ns1)])
570570
self.assertRaises(RPCError, inst.methodSignature, 'ns1.foo')
571571

572-
def test_multicall_recursion_forbidden(self):
572+
def test_multicall_faults_for_recursion(self):
573+
from supervisor.xmlrpc import Faults
573574
inst = self._makeOne()
574-
call = {'methodName':'system.multicall'}
575-
multiproduce = inst.multicall([call])
576-
result = multiproduce()
575+
calls = [{'methodName':'system.multicall'}]
576+
results = inst.multicall(calls)
577577
self.assertEqual(
578-
result,
579-
[{'faultCode': 2, 'faultString': 'INCORRECT_PARAMETERS'}]
578+
results,
579+
[{'faultCode': Faults.INCORRECT_PARAMETERS,
580+
'faultString': ('INCORRECT_PARAMETERS: Recursive '
581+
'system.multicall forbidden')}]
580582
)
581583

582-
def test_multicall_other_exception(self):
584+
def test_multicall_faults_for_missing_methodName(self):
585+
from supervisor.xmlrpc import Faults
583586
inst = self._makeOne()
584-
call = {} # no methodName
585-
multiproduce = inst.multicall([call])
586-
result = multiproduce()
587-
self.assertEqual(len(result), 1)
588-
self.assertEqual(result[0]['faultCode'], 1)
587+
calls = [{}]
588+
results = inst.multicall(calls)
589+
self.assertEqual(
590+
results,
591+
[{'faultCode': Faults.INCORRECT_PARAMETERS,
592+
'faultString': 'INCORRECT_PARAMETERS: No methodName'}]
593+
)
589594

590-
def test_multicall_no_calls(self):
595+
def test_multicall_faults_for_methodName_bad_namespace(self):
596+
from supervisor.xmlrpc import Faults
591597
inst = self._makeOne()
592-
multiproduce = inst.multicall([])
593-
result = multiproduce()
594-
self.assertEqual(result, [])
598+
calls = [{'methodName': 'bad.stopProcess'}]
599+
results = inst.multicall(calls)
600+
self.assertEqual(
601+
results,
602+
[{'faultCode': Faults.UNKNOWN_METHOD,
603+
'faultString': 'UNKNOWN_METHOD'}]
604+
)
595605

596-
def test_multicall_callback_raises_RPCError(self):
597-
from supervisor.xmlrpc import RPCError, Faults
606+
def test_multicall_faults_for_methodName_good_ns_bad_method(self):
607+
from supervisor.xmlrpc import Faults
598608
class DummyNamespace(object):
599-
def foo(self):
600-
""" @param string name The thing"""
601-
raise RPCError(Faults.UNKNOWN_METHOD)
602-
609+
pass
603610
ns1 = DummyNamespace()
604611
inst = self._makeOne([('ns1', ns1)])
605-
multiproduce = inst.multicall([{'methodName':'ns1.foo'}])
606-
result = multiproduce()
612+
calls = [{'methodName': 'ns1.bad'}]
613+
results = inst.multicall(calls)
607614
self.assertEqual(
608-
result, [{'faultString': 'UNKNOWN_METHOD', 'faultCode': 1}]
615+
results,
616+
[{'faultCode': Faults.UNKNOWN_METHOD,
617+
'faultString': 'UNKNOWN_METHOD'}]
609618
)
610619

611-
def test_multicall_callback_returns_function_returns_NOT_DONE_YET(self):
620+
def test_multicall_returns_empty_results_for_empty_calls(self):
621+
inst = self._makeOne()
622+
calls = []
623+
results = inst.multicall(calls)
624+
self.assertEqual(results, [])
625+
626+
def test_multicall_performs_noncallback_functions_serially(self):
627+
class DummyNamespace(object):
628+
def say(self, name):
629+
""" @param string name Process name"""
630+
return name
631+
ns1 = DummyNamespace()
632+
inst = self._makeOne([('ns1', ns1)])
633+
calls = [
634+
{'methodName': 'ns1.say', 'params': ['Alvin']},
635+
{'methodName': 'ns1.say', 'params': ['Simon']},
636+
{'methodName': 'ns1.say', 'params': ['Theodore']}
637+
]
638+
results = inst.multicall(calls)
639+
self.assertEqual(results, ['Alvin', 'Simon', 'Theodore'])
640+
641+
def test_multicall_catches_noncallback_exceptions(self):
642+
import errno
643+
from supervisor.xmlrpc import RPCError, Faults
644+
class DummyNamespace(object):
645+
def bad_name(self):
646+
raise RPCError(Faults.BAD_NAME, 'foo')
647+
def os_error(self):
648+
raise OSError(errno.ENOENT)
649+
ns1 = DummyNamespace()
650+
inst = self._makeOne([('ns1', ns1)])
651+
calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]
652+
results = inst.multicall(calls)
653+
654+
bad_name = {'faultCode': Faults.BAD_NAME,
655+
'faultString': 'BAD_NAME: foo'}
656+
os_error = {'faultCode': Faults.FAILED,
657+
'faultString': "FAILED: %s:2" % OSError}
658+
self.assertEqual(results, [bad_name, os_error])
659+
660+
def test_multicall_catches_callback_exceptions(self):
661+
import errno
662+
from supervisor.xmlrpc import RPCError, Faults
612663
from supervisor.http import NOT_DONE_YET
613664
class DummyNamespace(object):
614-
def __init__(self):
615-
self.results = [NOT_DONE_YET, 1]
616-
def foo(self):
617-
""" @param string name The thing"""
665+
def bad_name(self):
666+
def inner():
667+
raise RPCError(Faults.BAD_NAME, 'foo')
668+
return inner
669+
def os_error(self):
618670
def inner():
619-
return self.results.pop(0)
671+
raise OSError(errno.ENOENT)
620672
return inner
621673
ns1 = DummyNamespace()
622674
inst = self._makeOne([('ns1', ns1)])
623-
multiproduce = inst.multicall([{'methodName':'ns1.foo'}])
624-
result = multiproduce()
625-
self.assertEqual(
626-
result,
627-
NOT_DONE_YET
628-
)
629-
result = multiproduce()
630-
self.assertEqual(
631-
result,
632-
[1]
633-
)
634-
635-
def test_multicall_callback_returns_function_raises_RPCError(self):
636-
from supervisor.xmlrpc import Faults, RPCError
675+
calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]
676+
callback = inst.multicall(calls)
677+
results = NOT_DONE_YET
678+
while results is NOT_DONE_YET:
679+
results = callback()
680+
681+
bad_name = {'faultCode': Faults.BAD_NAME,
682+
'faultString': 'BAD_NAME: foo'}
683+
os_error = {'faultCode': Faults.FAILED,
684+
'faultString': "FAILED: %s:2" % OSError}
685+
self.assertEqual(results, [bad_name, os_error])
686+
687+
def test_multicall_performs_callback_functions_serially(self):
688+
from supervisor.http import NOT_DONE_YET
637689
class DummyNamespace(object):
638-
def foo(self):
639-
""" @param string name The thing"""
690+
def __init__(self):
691+
self.stop_results = [NOT_DONE_YET, NOT_DONE_YET,
692+
NOT_DONE_YET, 'stop result']
693+
self.start_results = ['start result']
694+
def stopProcess(self, name):
695+
def inner():
696+
result = self.stop_results.pop(0)
697+
if result is not NOT_DONE_YET:
698+
self.stopped = True
699+
return result
700+
return inner
701+
def startProcess(self, name):
640702
def inner():
641-
raise RPCError(Faults.UNKNOWN_METHOD)
703+
if not self.stopped:
704+
raise Exception("This should not raise")
705+
return self.start_results.pop(0)
642706
return inner
643707
ns1 = DummyNamespace()
644708
inst = self._makeOne([('ns1', ns1)])
645-
multiproduce = inst.multicall([{'methodName':'ns1.foo'}])
646-
result = multiproduce()
647-
self.assertEqual(
648-
result,
649-
[{'faultString': 'UNKNOWN_METHOD', 'faultCode': 1}],
650-
)
651-
709+
calls = [{'methodName': 'ns1.stopProcess',
710+
'params': {'name': 'foo'}},
711+
{'methodName': 'ns1.startProcess',
712+
'params': {'name': 'foo'}}]
713+
callback = inst.multicall(calls)
714+
results = NOT_DONE_YET
715+
while results is NOT_DONE_YET:
716+
results = callback()
717+
self.assertEqual(results, ['stop result', 'start result'])
652718

653719
class Test_gettags(unittest.TestCase):
654720
def _callFUT(self, comment):

supervisor/web.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -414,20 +414,27 @@ def stopdone():
414414
return stopdone
415415

416416
elif action == 'restart':
417-
callback = rpcinterface.system.multicall(
417+
results_or_callback = rpcinterface.system.multicall(
418418
[ {'methodName':'supervisor.stopProcess',
419419
'params': [namespec]},
420420
{'methodName':'supervisor.startProcess',
421421
'params': [namespec]},
422422
]
423423
)
424-
def restartprocess():
425-
result = callback()
426-
if result is NOT_DONE_YET:
427-
return NOT_DONE_YET
428-
return 'Process %s restarted' % namespec
429-
restartprocess.delay = 0.05
430-
return restartprocess
424+
if callable(results_or_callback):
425+
callback = results_or_callback
426+
def restartprocess():
427+
results = callback()
428+
if results is NOT_DONE_YET:
429+
return NOT_DONE_YET
430+
return 'Process %s restarted' % namespec
431+
restartprocess.delay = 0.05
432+
return restartprocess
433+
else:
434+
def restartdone():
435+
return 'Process %s restarted' % namespec
436+
restartdone.delay = 0.05
437+
return restartdone
431438

432439
elif action == 'clearlog':
433440
try:

0 commit comments

Comments
 (0)