Skip to content

Commit 89eb8ef

Browse files
pbugniivan-cachen2401Amy Chen
authored
IRONN-270 hotfix. (#4423)
See also https://movember.atlassian.net/browse/IRONN-270 The `TimeoutLock` used to prevent concurrent EMPRO trigger states overwrites was expiring prior to completion. Elaborate chain of events needed to reproduce the problem, but the existing 60 second timeout for any single user's updates to their trigger_states->triggers data was inadequate when other jobs (such as `update_patients_task`) came in before completion of trigger processing. The end result was an area of code intended to be locked in a critical section, would obtain a fresh lock even if one was in use, when it had been longer than the expiration time. This PR gives more than adequate space (2 hours rather than 60 seconds). --------- Co-authored-by: Ivan Cvitkovic <[email protected]> Co-authored-by: Amy Chen <[email protected]> Co-authored-by: Amy Chen <[email protected]>
1 parent 212e244 commit 89eb8ef

File tree

3 files changed

+29
-4
lines changed

3 files changed

+29
-4
lines changed

portal/static/js/src/admin.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ let requestTimerId = 0;
143143
errorElement.innerHTML = errorMessage;
144144
}
145145
},
146+
clearError: function() {
147+
var errorElement = document.getElementById("admin-table-error-message");
148+
if (errorElement) {
149+
errorElement.innerHTML = "";
150+
}
151+
},
146152
injectDependencies: function () {
147153
var self = this;
148154
window.portalModules =
@@ -200,6 +206,8 @@ let requestTimerId = 0;
200206
);
201207
return;
202208
}
209+
//reset error
210+
this.clearError();
203211
this.patientDataAjaxRequest(params);
204212
},
205213
patientDataAjaxRequest: function (params) {
@@ -219,6 +227,12 @@ let requestTimerId = 0;
219227
}
220228
self.accessed = true;
221229
params.success(results);
230+
}).fail(function(xhr, status) {
231+
console.log("Error ", xhr);
232+
console.log("status", status);
233+
self.setError("Error occurred loading data.");
234+
params.success([]);
235+
self.accessed = true;
222236
});
223237
},
224238
handleCurrentUser: function () {
@@ -1229,11 +1243,12 @@ let requestTimerId = 0;
12291243
sync: true,
12301244
},
12311245
function (data) {
1246+
prefData = data || self.getDefaultTablePreference();
1247+
self.currentTablePreference = prefData;
1248+
12321249
if (!data || data.error) {
12331250
return false;
12341251
}
1235-
prefData = data || self.getDefaultTablePreference();
1236-
self.currentTablePreference = prefData;
12371252

12381253
if (setFilter) {
12391254
//set filter values
@@ -1303,6 +1318,7 @@ let requestTimerId = 0;
13031318
for (var item in prefData.filters) {
13041319
fname = "#adminTable .bootstrap-table-filter-control-" + item;
13051320
if ($(fname).length === 0) {
1321+
prefData.filters[item] = null;
13061322
continue;
13071323
}
13081324
//note this is based on the trigger event for filtering specify in the plugin

portal/templates/admin/patients_by_org.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ <h4 class="tnth-headline">{{_("Patient List")}}</h4>
6565
</thead>
6666
</table>
6767
</div>
68-
<div id="admin-table-error-message" class="text-danger smaller-text"></div>
68+
<br/>
69+
<div id="admin-table-error-message" class="text-danger"></div>
6970
{{ExportPopover()}}
7071
</div>
7172
{{ajaxDataScript(research_study_id=0)}}

portal/trigger_states/empro_states.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from ..timeout_lock import TimeoutLock, LockTimeout
2727

2828
EMPRO_LOCK_KEY = "empro-trigger-state-lock-{user_id}"
29+
EMPRO_LOCK_EXPIRATION = 2*60*60 # yes, 2 hours given interruptions by big jobs (see IRONN-270)
2930
OPT_OUT_DELAY = 1800 # seconds to allow user to provide opt-out choices
3031

3132
class EMPRO_state(StateMachine):
@@ -296,6 +297,9 @@ def process_processed(ts):
296297
# necessary to make deep copy in order to update DB JSON
297298
triggers = copy.deepcopy(ts.triggers)
298299
triggers['action_state'] = 'not applicable'
300+
if 'actions' in triggers:
301+
current_app.logger.error(
302+
f"unexpected existing 'actions' in trigger_states.triggers({ts.id}): {triggers['actions']}")
299303
triggers['actions'] = dict()
300304
triggers['actions']['email'] = list()
301305

@@ -413,6 +417,7 @@ def process_pending_actions(ts):
413417
try:
414418
with TimeoutLock(
415419
key=EMPRO_LOCK_KEY.format(user_id=ts.user_id),
420+
expires=EMPRO_LOCK_EXPIRATION,
416421
timeout=NEVER_WAIT):
417422
process_processed(ts)
418423
db.session.commit()
@@ -425,6 +430,7 @@ def process_pending_actions(ts):
425430
try:
426431
with TimeoutLock(
427432
key=EMPRO_LOCK_KEY.format(user_id=ts.user_id),
433+
expires=EMPRO_LOCK_EXPIRATION,
428434
timeout=NEVER_WAIT):
429435
process_pending_actions(ts)
430436
db.session.commit()
@@ -555,7 +561,9 @@ def extract_observations(questionnaire_response_id, override_state=False):
555561

556562
# given asynchronous possibility, require user's EMPRO lock
557563
with TimeoutLock(
558-
key=EMPRO_LOCK_KEY.format(user_id=qnr.subject_id), timeout=60):
564+
key=EMPRO_LOCK_KEY.format(user_id=qnr.subject_id),
565+
expires=EMPRO_LOCK_EXPIRATION,
566+
timeout=EMPRO_LOCK_EXPIRATION+60):
559567
ts = users_trigger_state(qnr.subject_id)
560568
sm = EMPRO_state(ts)
561569
if not override_state:

0 commit comments

Comments
 (0)