Skip to content

Commit

Permalink
Add timeouts for exports
Browse files Browse the repository at this point in the history
All three of the current exporters use a subprocess call to perform the
export. To prevent the process from hanging, we add a condition to kill
th process after a specified period of time.
  • Loading branch information
NyanHelsing committed May 11, 2018
1 parent fc0d556 commit 4e0a287
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 40 deletions.
4 changes: 2 additions & 2 deletions mfr/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def make_provider(name, request, url):
namespace='mfr.providers',
name=name.lower(),
invoke_on_load=True,
invoke_args=(request, url, ),
invoke_args=(request, url)
).driver
except RuntimeError:
raise exceptions.MakeProviderError(
Expand Down Expand Up @@ -49,7 +49,7 @@ def make_exporter(name, source_file_path, output_file_path, format):
namespace='mfr.exporters',
name=normalized_name,
invoke_on_load=True,
invoke_args=(normalized_name, source_file_path, output_file_path, format),
invoke_args=(normalized_name, source_file_path, output_file_path, format)
).driver
except RuntimeError:
raise exceptions.MakeExporterError(
Expand Down
Empty file.
5 changes: 5 additions & 0 deletions mfr/extensions/default/exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from mfr.core.extension import BaseExporter

class DefaultExporter(BaseExporter):
def export():
pass
50 changes: 37 additions & 13 deletions mfr/extensions/jsc3d/export.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import os
from os.path import (
basename,
splitext
)
import shutil
import subprocess
from subprocess import (
DEVNULL,
CalledProcessError,
TimeoutExpired,
check_call
)
from http import HTTPStatus
from tempfile import NamedTemporaryFile

from mfr.core import exceptions
from mfr.core.extension import BaseExporter
from mfr.extensions.jsc3d.settings import (FREECAD_BIN,
FREECAD_CONVERT_SCRIPT)
from mfr.extensions.jsc3d.settings import (
FREECAD_BIN,
TIMEOUT,
CONVERSION_SCRIPT
)


class JSC3DExporter(BaseExporter):
Expand All @@ -22,16 +33,14 @@ def export(self):
temp_source_file.name = self.source_file_path + '.step'
shutil.copy2(self.source_file_path, temp_source_file.name)

subprocess.check_call([
FREECAD_BIN,
FREECAD_CONVERT_SCRIPT,
temp_source_file.name,
self.output_file_path,
# silence output from freecadcmd
], stdout=subprocess.DEVNULL)
check_call(
[FREECAD_BIN, CONVERSION_SCRIPT, temp_source_file.name, self.output_file_path],
stdout=DEVNULL, # silence output from freecadcmd
timeout=TIMEOUT
)

except subprocess.CalledProcessError as err:
name, extension = os.path.splitext(os.path.split(self.source_file_path)[-1])
except CalledProcessError as err:
name, extension = splitext(basename(self.source_file_path))
raise exceptions.SubprocessError(
'Unable to export the file in the requested format, please try again later.',
process='freecad',
Expand All @@ -42,3 +51,18 @@ def export(self):
extension=extension or '',
exporter_class='jsc3d',
)

except TimeoutExpired as err:
# The return code 52 is not re error code returned by the
# subprocess, but the error given to it by this waterbutler
# proccesss, for timing out.
raise exceptions.SubprocessError(
'JSC3D Conversion timed out.',
code=504,
process='freecad',
cmd=str(err.cmd),
returncode=52,
path=str(self.source_file_path),
extension=extension or '',
exporter_class='jsc3d'
)
3 changes: 2 additions & 1 deletion mfr/extensions/jsc3d/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
config = settings.child('JSC3D_EXTENSION_CONFIG')

FREECAD_BIN = config.get('FREECAD_BIN', '/usr/bin/freecadcmd')
FREECAD_CONVERT_SCRIPT = config.get('FREECAD_CONVERT_SCRIPT', '/code/mfr/extensions/jsc3d/freecad_converter.py')
CONVERSION_SCRIPT = config.get('FREECAD_CONVERT_SCRIPT', '/code/mfr/extensions/jsc3d/freecad_converter.py')
TIMEOUT = int(config.get('FREECAD_TIMEOUT', 10)) # In seconds
EXPORT_TYPE = config.get('EXPORT_TYPE', '.stl')
EXPORT_EXCLUSIONS = config.get('EXPORT_EXCLUSIONS', '.3ds .stl .obj .ctm').split(' ')
1 change: 1 addition & 0 deletions mfr/extensions/tabular/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@
})

PSPP_CONVERT_BIN = config.get('PSPP_CONVERT_BIN', '/usr/bin/pspp-convert')
PSPP_CONVERT_TIMEOUT = int(config.get('PSPP_CONVERT_TIMEOUT', 10)) # In seconds
42 changes: 31 additions & 11 deletions mfr/extensions/tabular/utilities.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import re
import subprocess
from subprocess import (
CalledProcessError,
TimeoutExpired,
check_call
)
from tempfile import NamedTemporaryFile

from mfr.core import exceptions
from mfr.extensions.tabular import compat, settings

from mfr.core.exceptions import SubprocessError
from mfr.extensions.tabular import compat
from mfr.extensions.tabular.settings import (
PSPP_CONVERT_BIN,
PSPP_CONVERT_TIMEOUT,
)

def header_population(headers):
"""make column headers from a list
Expand Down Expand Up @@ -48,13 +55,12 @@ def sav_to_csv(fp):
"""
csv_file = NamedTemporaryFile(mode='w+b', suffix='.csv')
try:
subprocess.check_call([
settings.PSPP_CONVERT_BIN,
fp.name,
csv_file.name,
])
except subprocess.CalledProcessError as err:
raise exceptions.SubprocessError(
check_call(
[PSPP_CONVERT_BIN, fp.name, csv_file.name],
timeout=PSPP_CONVERT_TIMEOUT
)
except CalledProcessError as err:
raise SubprocessError(
'Unable to convert the SPSS file to CSV, please try again later.',
code=500,
process='pspp',
Expand All @@ -64,4 +70,18 @@ def sav_to_csv(fp):
extension='sav',
exporter_class='tabular',
)
except TimeoutExpired as err:
# The return code 52 is not the error code returned by the
# subprocess, but the error given to it by this waterbutler
# proccesss, for timing out.
raise SubprocessError(
'CSV Conversion timed out.',
code=504,
process='pspp',
cmd=str(err.cmd),
returncode=52,
path=fp.name,
extension='sav',
exporter_class='tabular'
)
return csv_file
37 changes: 24 additions & 13 deletions mfr/extensions/unoconv/export.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
import os
import subprocess
from os.path import (
basename,
splitext
)
from subprocess import (
CalledProcessError,
run
)

from mfr.core import extension
from mfr.core import exceptions
from mfr.core.exceptions import SubprocessError
from mfr.core.extension import BaseExporter

from mfr.extensions.unoconv import settings
from mfr.extensions.unoconv.settings import (
ADDRESS,
PORT,
UNOCONV_TIMEOUT,
UNOCONV_BIN
)


class UnoconvExporter(extension.BaseExporter):
class UnoconvExporter(BaseExporter):

def export(self):
try:
subprocess.run([
settings.UNOCONV_BIN,
run([
UNOCONV_BIN,
'-n',
'-c', 'socket,host={},port={};urp;StarOffice.ComponentContext'.format(settings.ADDRESS, settings.PORT),
'-c', 'socket,host={},port={};urp;StarOffice.ComponentContext'.format(ADDRESS, PORT),
'-f', self.format,
'-o', self.output_file_path,
'-vvv',
self.source_file_path
], check=True, timeout=settings.UNOCONV_TIMEOUT)
except subprocess.CalledProcessError as err:
name, extension = os.path.splitext(os.path.split(self.source_file_path)[-1])
raise exceptions.SubprocessError(
], check=True, timeout=UNOCONV_TIMEOUT)
except CalledProcessError as err:
name, extension = splitext(basename(self.source_file_path))
raise SubprocessError(
'Unable to export the file in the requested format, please try again later.',
process='unoconv',
cmd=str(err.cmd),
Expand Down
6 changes: 6 additions & 0 deletions mfr/server/handlers/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ async def get(self):
self.source_file_path
)

if self.metadata.ext == ".{}".format(self.format):
with open(self.source_file_path.full_path, 'rb') as fp:
self._set_headers()
await self.write_stream(waterbutler.core.streams.FileStreamReader(fp))
return

exporter = utils.make_exporter(
self.metadata.ext,
self.source_file_path.full_path,
Expand Down

0 comments on commit 4e0a287

Please sign in to comment.