0005-forms-abort-autosave-after-200-ms-58276.patch
tests/form_pages/test_all.py | ||
---|---|---|
4276 | 4276 |
assert formdef.data_class().select()[0].data['1'] == 'foobar3' |
4277 | 4277 | |
4278 | 4278 | |
4279 |
def test_form_autosave_timeout(pub, monkeypatch): |
|
4280 |
from wcs.forms.root import FormPage |
|
4281 | ||
4282 |
monkeypatch.setattr(FormPage, 'AUTOSAVE_TIMEOUT', 0.0001) |
|
4283 | ||
4284 |
formdef = create_formdef() |
|
4285 |
formdef.fields = [ |
|
4286 |
fields.PageField(id='0', label='1st page', type='page'), |
|
4287 |
fields.StringField(id='1', label='string'), |
|
4288 |
fields.PageField(id='2', label='2nd page', type='page'), |
|
4289 |
fields.StringField(id='3', label='string 2'), |
|
4290 |
] |
|
4291 |
formdef.enable_tracking_codes = True |
|
4292 |
formdef.store() |
|
4293 | ||
4294 |
formdef.data_class().wipe() |
|
4295 |
app = get_app(pub) |
|
4296 |
resp = app.get('/test/') |
|
4297 |
resp.form['f1'] = 'foobar' |
|
4298 | ||
4299 |
resp = app.post('/test/autosave', params=resp.form.submit_fields()) |
|
4300 |
assert resp.json == {'reason': 'autosave took more than 0.0001 seconds', 'result': 'error'} |
|
4301 | ||
4302 | ||
4279 | 4303 |
def test_form_autosave_with_items_field(pub): |
4280 | 4304 |
formdef = create_formdef() |
4281 | 4305 |
formdef.data_class().wipe() |
wcs/forms/root.py | ||
---|---|---|
1297 | 1297 |
formdata.user = get_request().user |
1298 | 1298 |
formdata.store() |
1299 | 1299 | |
1300 |
AUTOSAVE_TIMEOUT = 0.2 |
|
1301 | ||
1300 | 1302 |
def autosave(self): |
1303 |
try: |
|
1304 |
with misc.timeout(self.AUTOSAVE_TIMEOUT): |
|
1305 |
return self._autosave() |
|
1306 |
except misc.TimeoutException: |
|
1307 |
get_request().ignore_session = True |
|
1308 |
return json.dumps( |
|
1309 |
{'result': 'error', 'reason': 'autosave took more than %s seconds' % self.AUTOSAVE_TIMEOUT} |
|
1310 |
) |
|
1311 | ||
1312 |
def _autosave(self): |
|
1301 | 1313 |
get_response().set_content_type('application/json') |
1302 | 1314 | |
1303 | 1315 |
def result_error(reason): |
wcs/qommon/misc.py | ||
---|---|---|
16 | 16 | |
17 | 17 |
import base64 |
18 | 18 |
import calendar |
19 |
import contextlib |
|
19 | 20 |
import datetime |
20 | 21 |
import decimal |
21 | 22 |
import hashlib |
... | ... | |
24 | 25 |
import json |
25 | 26 |
import os |
26 | 27 |
import re |
28 |
import signal |
|
27 | 29 |
import subprocess |
28 | 30 |
import time |
29 | 31 |
import unicodedata |
... | ... | |
1089 | 1091 |
} |
1090 | 1092 |
object_type_name = object_type_names.get(value.__class__, value.__class__.__name__) |
1091 | 1093 |
return object_type_name |
1094 | ||
1095 | ||
1096 |
class TimeoutException(Exception): |
|
1097 |
pass |
|
1098 | ||
1099 | ||
1100 |
def _alarm_handler_for_timeout(signum, frame): |
|
1101 |
raise TimeoutException |
|
1102 | ||
1103 | ||
1104 |
# this context manager cannot be nested, only one timeout can be set at a time, |
|
1105 |
# enclosing timer will be reset |
|
1106 |
@contextlib.contextmanager |
|
1107 |
def timeout(duration): |
|
1108 |
old_value = signal.getsignal(signal.SIGALRM) |
|
1109 |
try: |
|
1110 |
signal.signal(signal.SIGALRM, _alarm_handler_for_timeout) |
|
1111 |
try: |
|
1112 |
signal.setitimer(signal.ITIMER_REAL, duration) |
|
1113 |
yield |
|
1114 |
finally: |
|
1115 |
signal.setitimer(signal.ITIMER_REAL, 0) |
|
1116 |
finally: |
|
1117 |
signal.signal(signal.SIGALRM, old_value) |
|
1092 |
- |