Skip to content

Commit 462cb1c

Browse files
committed
refactored image generation
simpler implementation (w/o thread for process communication) makes it easier to reason about imo. also added explicit sys.exit() to jython/generate_images.py (which fixes an issue in which the process sometimes never terminated)
1 parent f45a013 commit 462cb1c

2 files changed

Lines changed: 57 additions & 99 deletions

File tree

‎generator.py‎

Lines changed: 56 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -142,89 +142,65 @@ def get_element_text(self, element):
142142
text = ''
143143
return text
144144

145-
class JythonImageProcess:
146-
'''Class to manage communicating with a remote process. We could eventually have multiple instances going at once.'''
147-
running_re = re.compile(r'^:RUNNING:(.+)$')
148-
success_re = re.compile(r'^:SUCCESS:$')
149-
failure_re = re.compile(r'^:FAILURE:$')
150-
151-
@staticmethod
152-
def make_jython_command(processing_py_jar="./processing-py.jar",
153-
javabin="java"):
154-
return [javabin, '-cp', processing_py_jar, 'org.python.util.jython']
155-
156-
@staticmethod
157-
def create_args(processing_py_jar, jython_dir, workitems):
158-
command = JythonImageProcess.make_jython_command(processing_py_jar)
159-
jython_script = os.path.join(jython_dir, 'generate_images.py')
160-
if not os.path.exists(jython_script):
161-
raise IOError("Can't find jython script to run...")
162-
command += [jython_script]
163-
command += ['--todo'] + ['{}:{}:{}'.format(ex['name'], ex['scriptfile'], ex['imagefile']) for ex in workitems.itervalues()]
164-
return command
165-
166-
def __init__(self, processing_py_jar, jython_dir, workitems):
167-
self.current = None
168-
self.generated = {}
169-
self.failed = {}
170-
self.workitems = workitems
171-
args = JythonImageProcess.create_args(processing_py_jar, jython_dir, workitems)
172-
print(args, file=sys.stderr)
173-
self.popen = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1) # line-buffered
174-
self.comthread = threading.Thread(target=self.consume_communication, args=())
175-
self.comthread.start()
176-
177-
def consume_communication(self):
178-
for line in iter(self.popen.stdout.readline, ''):
179-
running = JythonImageProcess.running_re.match(line)
180-
if running:
181-
self.current = self.workitems[running.group(1)]
182-
print("{} ... RUNNING: {}".format(self.desc, self.current['name']))
183-
continue
184-
success = JythonImageProcess.success_re.match(line)
185-
if success:
186-
print("{} ... SUCCESS: ".format(self.desc), end='')
187-
print_success("{} -> {}".format(self.current['name'], self.current['imagefile']))
188-
self.generated[self.current['name']] = self.current
189-
self.current = None
190-
continue
191-
failure = JythonImageProcess.failure_re.match(line)
192-
if failure:
193-
print("{} ... FAILURE: ".format(self.desc), end='')
194-
print_error(self.current['name'])
195-
self.failed[self.current['name']] = self.current
196-
self.current = None
197-
continue
198-
# Not a success or failure, must be debug messages
199-
print("{} ... DEBUG: {}".format(self.desc, line), end='')
200-
201-
def is_complete(self):
202-
return self.popen.poll() != None
203-
204-
def exited_well(self):
205-
return self.popen.returncode == 0
206-
207-
def print_debug(self):
208-
print("Debug from {}:".format(self.desc()))
209-
for line in self.err_blob:
210-
print("{}: {}".format(self.desc(), line))
211-
212-
@property
213-
def desc(self):
214-
return 'Image Process'
215-
216145
export_image_postlude = r'''
217146
save('{imagefile}')
218147
exit()
219148
'''
220149

221-
def generate_images(items_dict, to_update, src_dir, processing_py_jar, target_image_dir):
150+
# run an instance of generate_images.py, capturing and recording its output.
151+
# base_cmd should be a list-formatted command-line to run the
152+
# generate_images.py script, suitable for passing to subprocess.Popen;
153+
# workitems should be a list of dictionaries describing the scripts to be run.
154+
def image_worker(base_cmd, workitems):
155+
156+
workitems_cmdline_param = ['{}:{}:{}'.format(ex['name'], ex['scriptfile'], ex['imagefile']) \
157+
for ex in workitems.itervalues()]
158+
process = subprocess.Popen(base_cmd + workitems_cmdline_param, stdout=subprocess.PIPE,
159+
stderr=subprocess.STDOUT, bufsize=1)
160+
generated = {}
161+
failed = {}
162+
current = None
163+
164+
for line in iter(process.stdout.readline, ''):
165+
m = re.search(r'^:RUNNING:(.+)$', line)
166+
if m:
167+
current = workitems[m.group(1)]
168+
print("{} ... RUNNING: {}".format('Image process', current['name']))
169+
continue
170+
m = re.search(r'^:SUCCESS:$', line)
171+
if m:
172+
print("{} ... SUCCESS: ".format('Image process'), end='')
173+
print_success("{} -> {}".format(current['name'], current['imagefile']))
174+
generated[current['name']] = current
175+
current = None
176+
continue
177+
m = re.search(r'^:FAILURE:$', line)
178+
if m:
179+
print("{} ... FAILURE: ".format('Image process'), end='')
180+
print_error(current['name'])
181+
failed[current['name']] = current
182+
current = None
183+
continue
184+
# Not a success or failure, must be debug messages
185+
print("{} ... DEBUG: {}".format('Image process', line), end='')
186+
187+
process.wait()
188+
if process.returncode == 0:
189+
print("Image process terminated successfully.")
190+
else:
191+
print_error("Image process terminated unsuccessfully.")
192+
if current:
193+
failed[current['name']] = current
194+
return generated, failed
195+
196+
def generate_images(items_dict, to_update, src_dir, processing_py_jar,
197+
target_image_dir, javabin="java"):
222198
'''Generate images from examples and return the number of failures.'''
223199
workitems = {} # Examples to run
224200
work_dir = tempfile.mkdtemp(prefix='processing-py-site-build')
225-
jython_dir = os.path.join(src_dir, 'jython')
226-
if not os.path.exists(jython_dir):
227-
raise IOError("{} doesn't exist; can't generate images.".format(jython_dir))
201+
generate_img_script = os.path.join(src_dir, 'jython', 'generate_images.py')
202+
if not os.path.exists(generate_img_script):
203+
raise IOError("{} doesn't exist; can't generate images.".format(generate_img_script))
228204

229205
for name in to_update:
230206
item = items_dict[name]
@@ -245,31 +221,12 @@ def generate_images(items_dict, to_update, src_dir, processing_py_jar, target_im
245221
print("Skipping image generation, everything up to date")
246222
return 0
247223

248-
process = None
249-
generated = {}
250-
failed = {}
251-
252-
while workitems:
253-
if not process:
254-
print("Starting image process")
255-
process = JythonImageProcess(processing_py_jar, jython_dir, workitems)
224+
base_cmd = [javabin, "-cp", processing_py_jar, "org.python.util.jython",
225+
generate_img_script, '--todo']
256226

257-
if process.is_complete():
258-
if process.exited_well():
259-
print("{} terminated successfully.".format(process.desc))
260-
else:
261-
print_error("{} terminated unsuccessfully.".format(process.desc))
262-
if process.current:
263-
process.failed[process.current['name']] = process.current
264-
for workitem in process.generated.itervalues():
265-
generated[workitem['name']] = workitem
266-
del workitems[workitem['name']]
267-
for workitem in process.failed.itervalues():
268-
failed[workitem['name']] = workitem
269-
del workitems[workitem['name']]
270-
process = None
271-
272-
time.sleep(0.2)
227+
# TODO: use the multiprocessing module's Pool class to run multiple
228+
# instances of this at once
229+
generated, failed = image_worker(base_cmd, workitems)
273230

274231
print("Generated images:")
275232
for workitem in generated.itervalues():

‎jython/generate_images.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,4 @@ def gen(workitems):
111111
except:
112112
debug_error()
113113
sys.exit(1)
114+
sys.exit(0)

0 commit comments

Comments
 (0)