Skip to content

Commit fa28ce1

Browse files
committed
Support interactive mode with charmrun on one host
Launching with charmrun on one host with ++interactive command-line option will enter an interactive shell. For example: $ python3 -m charmrun.start +p4 ++interactive will start an interactive shell using 4 processes. In this mode, new chare types can be defined and created at run time. Similarly, interactive mode can also be entered from a python script by doing: charm.start(interactive=True)
1 parent 9be76b8 commit fa28ce1

File tree

3 files changed

+129
-1
lines changed

3 files changed

+129
-1
lines changed

charmpy/charm.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ def __init__(self):
9999
# maps cond_str to condition object. the condition object stores the lambda function associated with cond_str
100100
# TODO: remove old/unused condition strings
101101
self.wait_conditions = {}
102+
# store chare types defined after program start and other objects created
103+
# in interactive mode
104+
self.dynamic_register = {}
102105

103106
def handleGeneralError(self):
104107
import traceback
@@ -413,7 +416,7 @@ def _registerInternalChares(self):
413416
def _createInternalChares(self):
414417
Group(CharmRemote)
415418

416-
def start(self, entry, classes=[], modules=[]):
419+
def start(self, entry=None, classes=[], modules=[], interactive=False):
417420
"""
418421
Start Charmpy program.
419422
@@ -430,6 +433,13 @@ def start(self, entry, classes=[], modules=[]):
430433
arguments are passed to this method.
431434
"""
432435

436+
if interactive:
437+
from .interactive import InteractiveConsole as entry
438+
self.origStdinFd = os.dup(0)
439+
self.origStoutFd = os.dup(1)
440+
self.dynamic_register.update({'charm': charm, 'Chare': Chare, 'Group': Group,
441+
'Array': Array, 'Reducer': self.reducers})
442+
433443
if self.started:
434444
raise CharmPyError("charm.start() can only be called once")
435445
self.started = True
@@ -635,6 +645,12 @@ def exit(self, exit_code=0):
635645
def myPe(self):
636646
return charm.myPe()
637647

648+
def registerNewChareType(self, name, source):
649+
exec(source, charm.dynamic_register)
650+
chare_type = charm.dynamic_register[name]
651+
charm.register(chare_type)
652+
charm.registerInCharm(chare_type)
653+
638654

639655
def load_charm_library(charm):
640656
args = sys.argv

charmpy/interactive.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from . import charm, Chare, threaded
2+
import sys
3+
import time
4+
import re
5+
from code import InteractiveInterpreter
6+
import os
7+
8+
9+
class InteractiveConsole(Chare, InteractiveInterpreter):
10+
11+
def __init__(self, args):
12+
# restore original tty stdin and stdout (else readline won't work correctly)
13+
os.dup2(charm.origStdinFd, 0)
14+
os.dup2(charm.origStoutFd, 1)
15+
InteractiveInterpreter.__init__(self, locals=charm.dynamic_register)
16+
self.filename = '<console>'
17+
self.resetbuffer()
18+
# regexp to detect when user defines a new chare type
19+
self.regexpChareDefine = re.compile('class\s*(\S+)\s*\(.*Chare.*\)\s*:')
20+
21+
try:
22+
import readline
23+
import rlcompleter
24+
readline.parse_and_bind('tab: complete')
25+
except:
26+
pass
27+
28+
try:
29+
sys.ps1
30+
except AttributeError:
31+
sys.ps1 = '>>> '
32+
try:
33+
sys.ps2
34+
except AttributeError:
35+
sys.ps2 = '... '
36+
self.thisProxy.start()
37+
38+
def resetbuffer(self):
39+
self.buffer = []
40+
41+
def null(self):
42+
return
43+
44+
def write(self, data):
45+
# print(data)
46+
sys.stdout.write(data)
47+
sys.stdout.flush()
48+
# go through charm scheduler to keep things moving
49+
self.thisProxy.null(ret=True).get()
50+
51+
@threaded
52+
def start(self):
53+
self.write('CharmPy interactive shell\n')
54+
more = 0
55+
tick = time.time()
56+
while 1:
57+
# self.thisProxy.null(ret=True).get()
58+
# time.sleep(0.001)
59+
if time.time() - tick > 0.01:
60+
try:
61+
if more:
62+
prompt = sys.ps2
63+
else:
64+
prompt = sys.ps1
65+
try:
66+
line = self.raw_input(prompt)
67+
tick = time.time()
68+
except EOFError:
69+
self.write('\n')
70+
break
71+
else:
72+
more = self.push(line)
73+
except KeyboardInterrupt:
74+
self.write('\nKeyboardInterrupt\n')
75+
self.resetbuffer()
76+
more = 0
77+
78+
def push(self, line):
79+
self.buffer.append(line)
80+
source = '\n'.join(self.buffer)
81+
more = self.runsource(source, self.filename)
82+
if not more:
83+
self.resetbuffer()
84+
self.thisProxy.null(ret=True).get()
85+
return more
86+
87+
def runcode(self, code):
88+
chareTypeDefined = False
89+
for line in self.buffer:
90+
m = self.regexpChareDefine.search(line)
91+
if m is not None:
92+
newChareTypeName = m.group(1)
93+
source = '\n'.join(self.buffer)
94+
# print("Chare type '" + newChareTypeName + "' source:\n", source)
95+
charm.thisProxy.registerNewChareType(newChareTypeName, source, ret=True).get()
96+
chareTypeDefined = True
97+
if not chareTypeDefined:
98+
InteractiveInterpreter.runcode(self, code)
99+
100+
def raw_input(self, prompt=''):
101+
return input(prompt)

charmrun/start.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ def start(args=[]):
5050
if '++local' not in args and '++mpiexec' not in args and checkNodeListLocal(args):
5151
args.append('++local')
5252

53+
try:
54+
idx = args.index('++interactive')
55+
args[idx] = '-c'
56+
if os.name == 'nt':
57+
# workaround for how windows charmrun executable passes argument
58+
args.insert(idx + 1, '\"from charmpy import charm ; charm.start(interactive=True)\"')
59+
else:
60+
args.insert(idx + 1, 'from charmpy import charm ; charm.start(interactive=True)')
61+
except ValueError:
62+
pass
63+
5364
cmd = [os.path.join(os.path.dirname(__file__), 'charmrun')]
5465
cmd.append(sys.executable)
5566
cmd.extend(args)

0 commit comments

Comments
 (0)