Skip to content

Commit 55f1361

Browse files
authored
Clear and restore problematic NODE environment variables before calling orca (#1378)
* Add orca_env context manage to clear and restore environment variables that interfere with the correct functioning of orca. * Increase the orca request retry duration from 8s to 30s. 8 seconds was arbitrary, and we've received reports that it is sometimes not long enough. So increasing to 30s.
1 parent c9958e1 commit 55f1361

File tree

3 files changed

+88
-20
lines changed

3 files changed

+88
-20
lines changed

plotly/io/_orca.py

+47-20
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import threading
1010
import warnings
1111
from copy import copy
12+
from contextlib import contextmanager
1213

1314
import requests
1415
import retrying
@@ -836,6 +837,36 @@ def __repr__(self):
836837
del OrcaStatus
837838

838839

840+
@contextmanager
841+
def orca_env():
842+
"""
843+
Context manager to clear and restore environment variables that are
844+
problematic for orca to function properly
845+
846+
NODE_OPTIONS: When this variable is set, orca <v1.2 will have a
847+
segmentation fault due to an electron bug.
848+
See: https://github.com/electron/electron/issues/12695
849+
850+
ELECTRON_RUN_AS_NODE: When this environment variable is set the call
851+
to orca is transformed into a call to nodejs.
852+
See https://github.com/plotly/orca/issues/149#issuecomment-443506732
853+
"""
854+
clear_env_vars = ['NODE_OPTIONS', 'ELECTRON_RUN_AS_NODE']
855+
orig_env_vars = {}
856+
857+
try:
858+
# Clear and save
859+
orig_env_vars.update({
860+
var: os.environ.pop(var)
861+
for var in clear_env_vars
862+
if var in os.environ})
863+
yield
864+
finally:
865+
# Restore
866+
for var, val in orig_env_vars.items():
867+
os.environ[var] = val
868+
869+
839870
# Public orca server interaction functions
840871
# ----------------------------------------
841872
def validate_executable():
@@ -918,13 +949,6 @@ def validate_executable():
918949
formatted_path=formatted_path,
919950
instructions=install_location_instructions))
920951

921-
# Clear NODE_OPTIONS environment variable
922-
# ---------------------------------------
923-
# When this variable is set, orca <v1.2 will have a segmentation fault
924-
# due to an electron bug.
925-
# See: https://github.com/electron/electron/issues/12695
926-
os.environ.pop('NODE_OPTIONS', None)
927-
928952
# Run executable with --help and see if it's our orca
929953
# ---------------------------------------------------
930954
invalid_executable_msg = """
@@ -938,12 +962,13 @@ def validate_executable():
938962
instructions=install_location_instructions)
939963

940964
# ### Run with Popen so we get access to stdout and stderr
941-
p = subprocess.Popen(
942-
[executable, '--help'],
943-
stdout=subprocess.PIPE,
944-
stderr=subprocess.PIPE)
965+
with orca_env():
966+
p = subprocess.Popen(
967+
[executable, '--help'],
968+
stdout=subprocess.PIPE,
969+
stderr=subprocess.PIPE)
945970

946-
help_result, help_error = p.communicate()
971+
help_result, help_error = p.communicate()
947972

948973
if p.returncode != 0:
949974
err_msg = invalid_executable_msg + """
@@ -986,12 +1011,13 @@ def validate_executable():
9861011
# Get orca version
9871012
# ----------------
9881013
# ### Run with Popen so we get access to stdout and stderr
989-
p = subprocess.Popen(
990-
[executable, '--version'],
991-
stdout=subprocess.PIPE,
992-
stderr=subprocess.PIPE)
1014+
with orca_env():
1015+
p = subprocess.Popen(
1016+
[executable, '--version'],
1017+
stdout=subprocess.PIPE,
1018+
stderr=subprocess.PIPE)
9931019

994-
version_result, version_error = p.communicate()
1020+
version_result, version_error = p.communicate()
9951021

9961022
if p.returncode != 0:
9971023
raise ValueError(invalid_executable_msg + """
@@ -1171,8 +1197,9 @@ def ensure_server():
11711197
# Create subprocess that launches the orca server on the
11721198
# specified port.
11731199
DEVNULL = open(os.devnull, 'wb')
1174-
orca_state['proc'] = subprocess.Popen(cmd_list,
1175-
stdout=DEVNULL)
1200+
with orca_env():
1201+
orca_state['proc'] = subprocess.Popen(cmd_list,
1202+
stdout=DEVNULL)
11761203

11771204
# Update orca.status so the user has an accurate view
11781205
# of the state of the orca server
@@ -1191,7 +1218,7 @@ def ensure_server():
11911218
orca_state['shutdown_timer'] = t
11921219

11931220

1194-
@retrying.retry(wait_random_min=5, wait_random_max=10, stop_max_delay=8000)
1221+
@retrying.retry(wait_random_min=5, wait_random_max=10, stop_max_delay=30000)
11951222
def request_image_with_retrying(**kwargs):
11961223
"""
11971224
Helper method to perform an image request to a running orca server process

plotly/tests/test_orca/test_orca_server.py

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
# --------
1414
@pytest.fixture()
1515
def setup():
16+
# Set problematic environment variables
17+
os.environ['NODE_OPTIONS'] = '--max-old-space-size=4096'
18+
os.environ['ELECTRON_RUN_AS_NODE'] = '1'
19+
1620
# Reset orca state
1721
pio.orca.reset_status()
1822
pio.orca.config.restore_defaults()

plotly/tests/test_orca/test_to_image.py

+37
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,40 @@ def test_topojson_fig_to_image(topofig, format):
283283
def test_latex_fig_to_image(latexfig, format):
284284
img_bytes = pio.to_image(latexfig, format=format, width=700, height=500)
285285
assert_image_bytes(img_bytes, 'latexfig.' + format)
286+
287+
288+
# Environmnet variables
289+
# ---------------------
290+
def test_problematic_environment_variables(fig1, format):
291+
pio.orca.config.restore_defaults(reset_server=True)
292+
293+
os.environ['NODE_OPTIONS'] = '--max-old-space-size=4096'
294+
os.environ['ELECTRON_RUN_AS_NODE'] = '1'
295+
296+
# Do image export
297+
img_bytes = pio.to_image(fig1, format=format, width=700, height=500)
298+
assert_image_bytes(img_bytes, 'fig1.' + format)
299+
300+
# Check that environment variables were restored
301+
assert os.environ['NODE_OPTIONS'] == '--max-old-space-size=4096'
302+
assert os.environ['ELECTRON_RUN_AS_NODE'] == '1'
303+
304+
305+
# Invalid figure json
306+
# -------------------
307+
def test_invalid_figure_json():
308+
# Do image export
309+
bad_fig = {'foo': 'bar'}
310+
with pytest.raises(ValueError) as err:
311+
pio.to_image(bad_fig, format='png')
312+
313+
assert "Invalid value of type" in str(err.value)
314+
315+
with pytest.raises(ValueError) as err:
316+
pio.to_image(bad_fig, format='png', validate=False)
317+
318+
assert ('The image request was rejected by the orca conversion utility'
319+
in str(err.value))
320+
321+
assert ('400: invalid or malformed request syntax'
322+
in str(err.value))

0 commit comments

Comments
 (0)