• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 29038025281

09 Jul 2026 05:42PM UTC coverage: 65.97% (+0.005%) from 65.965%
29038025281

push

github

web-flow
TRUNK-6465: Prevent OrderServiceTest from deadlocking the connection pool (2.8.x) (#6276)

* TRUNK-6465: Prevent OrderServiceTest from deadlocking the connection pool

getNewOrderNumber_shouldAlwaysReturnUniqueOrderNumbersWhenCalledMultipleTimesWithoutSavingOrders
started 50 raw threads. Each call transiently holds two pooled
connections: one for the getNewOrderNumber transaction and one for the
REQUIRES_NEW transaction that increments the order number seed. With
hibernate.c3p0.max_size=50 and c3p0's default checkoutTimeout of 0
(wait forever), 50 concurrent outer transactions can drain the pool so
that every thread holds one connection while waiting eternally for a
second one nobody can release. The surefire fork then hangs silently
until the GitHub Actions 6 hour limit kills the job, which is what has
been wedging the Java 11 matrix leg (and historically Java 8, which was
skipped instead of fixed).

Run the 50 calls on a bounded pool of 20 threads so peak connection
demand stays below the pool cap, fail fast after 30 seconds instead of
hanging, and surface worker exceptions instead of swallowing them. This
removes the Java 8 skip since the deadlock is now structurally
impossible, and adds timeout-minutes to the build workflow so any
future wedge costs 40 minutes rather than 6 hours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQKxQruWri68zismbxhxW3

* TRUNK-6465: Harden the concurrency test failure path

Await executor termination after shutdownNow so straggler workers cannot
outlive the test method and muddy diagnosis of an original failure, turn
a bare TimeoutException on a wedge into an assertion failure that names
the connection pool deadlock and the ticket, and raise the workflow
timeout from 40 to 60 minutes so a degraded runner day (windows legs
normally reach 21 minutes) cannot false-positive the cap and fail-fast
cancel the sibling matrix legs.

Co-Authored-By: Claude Fable 5 <nor... (continued)

24248 of 36756 relevant lines covered (65.97%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.55
/api/src/main/java/org/openmrs/validator/PatientProgramValidator.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.validator;
11

12
import java.util.Date;
13
import java.util.HashSet;
14
import java.util.Set;
15

16
import org.openmrs.PatientProgram;
17
import org.openmrs.PatientState;
18
import org.openmrs.ProgramWorkflow;
19
import org.openmrs.annotation.Handler;
20
import org.openmrs.api.context.Context;
21
import org.openmrs.messagesource.MessageSourceService;
22
import org.openmrs.util.OpenmrsUtil;
23
import org.slf4j.Logger;
24
import org.slf4j.LoggerFactory;
25
import org.springframework.validation.Errors;
26
import org.springframework.validation.ValidationUtils;
27
import org.springframework.validation.Validator;
28

29
/**
30
 * This class validates a {@link PatientProgram} object
31
 *
32
 * @since 1.9
33
 */
34
@Handler(supports = { PatientProgram.class }, order = 50)
35
public class PatientProgramValidator implements Validator {
1✔
36
        
37
        private static final Logger log = LoggerFactory.getLogger(PatientProgramValidator.class);
1✔
38
        
39
        /**
40
         * @see org.springframework.validation.Validator#supports(java.lang.Class)
41
         */
42
        @Override
43
        public boolean supports(Class<?> c) {
44
                return PatientProgram.class.isAssignableFrom(c);
1✔
45
        }
46
        
47
        /**
48
         * Validates the given PatientProgram.
49
         *
50
         * @param obj The patient program to validate.
51
         * @param errors Errors
52
         * @see org.springframework.validation.Validator#validate(java.lang.Object,
53
         *      org.springframework.validation.Errors)
54
         * <strong>Should</strong> fail validation if obj is null
55
         * <strong>Should</strong> fail if the patient field is blank
56
         * <strong>Should</strong> fail if there is more than one patientState with the same states and startDates
57
         * <strong>Should</strong> fail if there is more than one state with a null start date in the same workflow
58
         * <strong>Should</strong> pass if the start date of the first patient state in the work flow is null
59
         * <strong>Should</strong> fail if any patient state has an end date before its start date
60
         * <strong>Should</strong> fail if the program property is null
61
         * <strong>Should</strong> fail if any patient states overlap each other in the same work flow
62
         * <strong>Should</strong> fail if a patientState has an invalid work flow state
63
         * <strong>Should</strong> fail if a patient program has duplicate states in the same work flow
64
         * <strong>Should</strong> fail if a patient is in multiple states in the same work flow
65
         * <strong>Should</strong> fail if a enrolled date is in future at the date it set
66
         * <strong>Should</strong> fail if a completion date is in future at the date it set
67
         * <strong>Should</strong> fail if a patient program has an enroll date after its completion
68
         * <strong>Should</strong> pass if a patient is in multiple states in different work flows
69
         * <strong>Should</strong> pass for a valid program
70
         * <strong>Should</strong> pass for patient states that have the same start dates in the same work flow
71
         * <strong>Should</strong> pass validation if field lengths are correct
72
         * <strong>Should</strong> fail validation if field lengths are not correct
73
         */
74
        @Override
75
        public void validate(Object obj, Errors errors) {
76
                log.debug("{}.validate...", this.getClass().getName());
1✔
77
                
78
                if (obj == null) {
1✔
79
                        throw new IllegalArgumentException("The parameter obj should not be null");
1✔
80
                }
81
                MessageSourceService mss = Context.getMessageSourceService();
1✔
82
                PatientProgram patientProgram = (PatientProgram) obj;
1✔
83
                ValidationUtils.rejectIfEmpty(errors, "patient", "error.required",
1✔
84
                    new Object[] { mss.getMessage("general.patient") });
1✔
85
                ValidationUtils.rejectIfEmpty(errors, "program", "error.required",
1✔
86
                    new Object[] { mss.getMessage("Program.program") });
1✔
87
                
88
                if (errors.hasErrors()) {
1✔
89
                        return;
1✔
90
                }
91
                
92
                ValidationUtils.rejectIfEmpty(errors, "dateEnrolled", "error.patientProgram.enrolledDateEmpty");
1✔
93
                
94
                Date today = new Date();
1✔
95
                if (patientProgram.getDateEnrolled() != null && today.before(patientProgram.getDateEnrolled())) {
1✔
96
                        errors.rejectValue("dateEnrolled", "error.patientProgram.enrolledDateDateCannotBeInFuture");
1✔
97
                }
98
                
99
                if (patientProgram.getDateCompleted() != null && today.before(patientProgram.getDateCompleted())) {
1✔
100
                        errors.rejectValue("dateCompleted", "error.patientProgram.completionDateCannotBeInFuture");
1✔
101
                }
102
                
103
                // if enrollment or complete date of program is in future or complete date has come before enroll date we should throw error
104
                if (patientProgram.getDateEnrolled() != null
1✔
105
                        && OpenmrsUtil.compareWithNullAsLatest(patientProgram.getDateCompleted(), patientProgram.getDateEnrolled()) < 0) {
1✔
106
                        errors.rejectValue("dateCompleted", "error.patientProgram.enrolledDateShouldBeBeforecompletionDate");
1✔
107
                }
108
                
109
                Set<ProgramWorkflow> workFlows = patientProgram.getProgram().getWorkflows();
1✔
110
                //Patient state validation is specific to a work flow
111
                for (ProgramWorkflow workFlow : workFlows) {
1✔
112
                        Set<PatientState> patientStates = patientProgram.getStates();
1✔
113
                        if (patientStates != null) {
1✔
114
                                //Set to store to keep track of unique valid state and start date combinations
115
                                Set<String> statesAndStartDates = new HashSet<>();
1✔
116
                                PatientState latestState = null;
1✔
117
                                boolean foundCurrentPatientState = false;
1✔
118
                                boolean foundStateWithNullStartDate = false;
1✔
119
                                for (PatientState patientState : patientStates) {
1✔
120
                                        if (patientState.getVoided()) {
1✔
121
                                                continue;
1✔
122
                                        }
123
                                        
124
                                        String missingRequiredFieldCode = null;
1✔
125
                                        //only the initial state can have a null start date
126
                                        if (patientState.getStartDate() == null) {
1✔
127
                                                if (foundStateWithNullStartDate) {
1✔
128
                                                        missingRequiredFieldCode = "general.dateStart";
1✔
129
                                                } else {
130
                                                        foundStateWithNullStartDate = true;
1✔
131
                                                }
132
                                        } else if (patientState.getState() == null) {
1✔
133
                                                missingRequiredFieldCode = "State.state";
1✔
134
                                        }
135
                                        
136
                                        if (missingRequiredFieldCode != null) {
1✔
137
                                                errors.rejectValue("states", "PatientState.error.requiredField", new Object[] { mss
1✔
138
                                                        .getMessage(missingRequiredFieldCode) }, null);
1✔
139
                                                return;
1✔
140
                                        }
141
                                        
142
                                        //state should belong to one of the workflows in the program
143
                                        // note that we are iterating over getAllWorkflows() here because we want to include
144
                                        // retired workflows, and the workflows variable does not include retired workflows
145
                                        boolean isValidPatientState = false;
1✔
146
                                        for (ProgramWorkflow wf : patientProgram.getProgram().getAllWorkflows()) {
1✔
147
                                                if (wf.getStates().contains(patientState.getState())) {
1✔
148
                                                        isValidPatientState = true;
1✔
149
                                                        break;
1✔
150
                                                }
151
                                        }
1✔
152
                                        
153
                                        if (!isValidPatientState) {
1✔
154
                                                errors.rejectValue("states", "PatientState.error.invalidPatientState",
1✔
155
                                                    new Object[] { patientState }, null);
156
                                                return;
1✔
157
                                        }
158
                                        
159
                                        //will validate it with other states in its workflow
160
                                        if (!patientState.getState().getProgramWorkflow().equals(workFlow)) {
1✔
161
                                                continue;
1✔
162
                                        }
163
                                        
164
                                        if (OpenmrsUtil.compareWithNullAsLatest(patientState.getEndDate(), patientState.getStartDate()) < 0) {
1✔
165
                                                errors.rejectValue("states", "PatientState.error.endDateCannotBeBeforeStartDate");
1✔
166
                                                return;
1✔
167
                                        } else if (statesAndStartDates.contains(patientState.getState().getUuid() + ""
1✔
168
                                                + patientState.getStartDate())) {
1✔
169
                                                // we already have a patient state with the same work flow state and start date
170
                                                errors.rejectValue("states", "PatientState.error.duplicatePatientStates");
1✔
171
                                                return;
1✔
172
                                        }
173
                                        
174
                                        //Ensure that the patient is only in one state at a given time
175
                                        if (!foundCurrentPatientState && patientState.getEndDate() == null) {
1✔
176
                                                foundCurrentPatientState = true;
1✔
177
                                        } else if (foundCurrentPatientState && patientState.getEndDate() == null) {
1✔
178
                                                errors.rejectValue("states", "PatientProgram.error.cannotBeInMultipleStates");
1✔
179
                                                return;
1✔
180
                                        }
181
                                        
182
                                        if (latestState == null) {
1✔
183
                                                latestState = patientState;
1✔
184
                                        } else {
185
                                                if (patientState.compareTo(latestState) > 0) {
1✔
186
                                                        //patient should have already left this state since it is older
187
                                                        if (latestState.getEndDate() == null) {
1✔
188
                                                                errors.rejectValue("states", "PatientProgram.error.cannotBeInMultipleStates");
×
189
                                                                return;
×
190
                                                        } else if (OpenmrsUtil.compareWithNullAsEarliest(patientState.getStartDate(), latestState
1✔
191
                                                                .getEndDate()) < 0) {
1✔
192
                                                                //current state was started before a previous state was ended
193
                                                                errors.rejectValue("states", "PatientProgram.error.foundOverlappingStates", new Object[] {
1✔
194
                                                                        patientState.getStartDate(), latestState.getEndDate() }, null);
1✔
195
                                                                return;
1✔
196
                                                        }
197
                                                        latestState = patientState;
1✔
198
                                                } else if (patientState.compareTo(latestState) < 0) {
1✔
199
                                                        //patient should have already left this state since it is older
200
                                                        if (patientState.getEndDate() == null) {
1✔
201
                                                                errors.rejectValue("states", "PatientProgram.error.cannotBeInMultipleStates");
×
202
                                                                return;
×
203
                                                        } else if (OpenmrsUtil.compareWithNullAsEarliest(latestState.getStartDate(), patientState
1✔
204
                                                                .getEndDate()) < 0) {
1✔
205
                                                                //latest state was started before a previous state was ended
206
                                                                errors.rejectValue("states", "PatientProgram.error.foundOverlappingStates");
×
207
                                                                return;
×
208
                                                        }
209
                                                }
210
                                        }
211
                                        
212
                                        statesAndStartDates.add(patientState.getState().getUuid() + "" + patientState.getStartDate());
1✔
213
                                }
1✔
214
                        }
215
                }
1✔
216
                ValidateUtil.validateFieldLengths(errors, obj.getClass(), "voidReason");
1✔
217
                //
218
        }
1✔
219
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc