Skip to content

Commit 52e2007

Browse files
author
Jörg Heffner
committed
Closes Taskana#2374: Deadlock when creating HistoryEvents from many connections simultaneously
-Write operations are now performed with the regular TaskanEngine and its' SqlSession and TransactionFactory which provides the needed transactionality and doesn't open multiple connections -Read operations are still performed by the TaskanaHistoryEngine
1 parent fdbc13e commit 52e2007

File tree

8 files changed

+106
-64
lines changed

8 files changed

+106
-64
lines changed

history/taskana-simplehistory-provider/src/main/java/pro/taskana/simplehistory/impl/SimpleHistoryServiceImpl.java

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package pro.taskana.simplehistory.impl;
22

3+
import java.lang.reflect.Field;
34
import java.sql.SQLException;
45
import java.time.Instant;
56
import java.util.List;
7+
import org.apache.ibatis.session.SqlSession;
68
import org.slf4j.Logger;
79
import org.slf4j.LoggerFactory;
810
import pro.taskana.common.api.TaskanaEngine;
911
import pro.taskana.common.api.TaskanaRole;
1012
import pro.taskana.common.api.exceptions.InvalidArgumentException;
1113
import pro.taskana.common.api.exceptions.NotAuthorizedException;
14+
import pro.taskana.common.api.exceptions.SystemException;
15+
import pro.taskana.common.internal.TaskanaEngineImpl;
1216
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryEventMapper;
1317
import pro.taskana.simplehistory.impl.classification.ClassificationHistoryQuery;
1418
import pro.taskana.simplehistory.impl.task.TaskHistoryEventMapper;
@@ -43,61 +47,75 @@ public void initialize(TaskanaEngine taskanaEngine) {
4347
taskanaEngine.getConfiguration().getSchemaName());
4448
}
4549

46-
this.taskHistoryEventMapper =
47-
this.taskanaHistoryEngine.getSqlSession().getMapper(TaskHistoryEventMapper.class);
48-
this.workbasketHistoryEventMapper =
49-
this.taskanaHistoryEngine.getSqlSession().getMapper(WorkbasketHistoryEventMapper.class);
50-
this.classificationHistoryEventMapper =
51-
this.taskanaHistoryEngine.getSqlSession().getMapper(ClassificationHistoryEventMapper.class);
52-
this.userMapper = taskanaHistoryEngine.getSqlSession().getMapper(UserMapper.class);
50+
Field sessionManager = null;
51+
try {
52+
sessionManager = TaskanaEngineImpl.class.getDeclaredField("sessionManager");
53+
sessionManager.setAccessible(true);
54+
} catch (NoSuchFieldException e) {
55+
throw new SystemException("SQL Session could not be retrieved. Aborting Startup");
56+
}
57+
try {
58+
SqlSession sqlSession = (SqlSession) sessionManager.get(taskanaEngine);
59+
if (!sqlSession
60+
.getConfiguration()
61+
.getMapperRegistry()
62+
.hasMapper(TaskHistoryEventMapper.class)) {
63+
sqlSession.getConfiguration().addMapper(TaskHistoryEventMapper.class);
64+
}
65+
if (!sqlSession
66+
.getConfiguration()
67+
.getMapperRegistry()
68+
.hasMapper(WorkbasketHistoryEventMapper.class)) {
69+
70+
sqlSession.getConfiguration().addMapper(WorkbasketHistoryEventMapper.class);
71+
}
72+
if (!sqlSession
73+
.getConfiguration()
74+
.getMapperRegistry()
75+
.hasMapper(ClassificationHistoryEventMapper.class)) {
76+
77+
sqlSession.getConfiguration().addMapper(ClassificationHistoryEventMapper.class);
78+
}
79+
80+
this.taskHistoryEventMapper = sqlSession.getMapper(TaskHistoryEventMapper.class);
81+
this.workbasketHistoryEventMapper = sqlSession.getMapper(WorkbasketHistoryEventMapper.class);
82+
this.classificationHistoryEventMapper =
83+
sqlSession.getMapper(ClassificationHistoryEventMapper.class);
84+
this.userMapper = sqlSession.getMapper(UserMapper.class);
85+
} catch (IllegalAccessException e) {
86+
throw new SystemException(
87+
"TASKANA engine of Session Manager could not be retrieved. Aborting Startup");
88+
}
5389
}
5490

5591
@Override
5692
public void create(TaskHistoryEvent event) {
57-
try {
58-
taskanaHistoryEngine.openConnection();
59-
if (event.getCreated() == null) {
60-
Instant now = Instant.now();
61-
event.setCreated(now);
62-
}
63-
taskHistoryEventMapper.insert(event);
64-
} catch (SQLException e) {
65-
LOGGER.error("Error while inserting task history event into database", e);
66-
} finally {
67-
taskanaHistoryEngine.returnConnection();
93+
94+
if (event.getCreated() == null) {
95+
Instant now = Instant.now();
96+
event.setCreated(now);
6897
}
98+
taskHistoryEventMapper.insert(event);
6999
}
70100

71101
@Override
72102
public void create(WorkbasketHistoryEvent event) {
73-
try {
74-
taskanaHistoryEngine.openConnection();
75-
if (event.getCreated() == null) {
76-
Instant now = Instant.now();
77-
event.setCreated(now);
78-
}
79-
workbasketHistoryEventMapper.insert(event);
80-
} catch (SQLException e) {
81-
LOGGER.error("Error while inserting workbasket history event into database", e);
82-
} finally {
83-
taskanaHistoryEngine.returnConnection();
103+
104+
if (event.getCreated() == null) {
105+
Instant now = Instant.now();
106+
event.setCreated(now);
84107
}
108+
workbasketHistoryEventMapper.insert(event);
85109
}
86110

87111
@Override
88112
public void create(ClassificationHistoryEvent event) {
89-
try {
90-
taskanaHistoryEngine.openConnection();
91-
if (event.getCreated() == null) {
92-
Instant now = Instant.now();
93-
event.setCreated(now);
94-
}
95-
classificationHistoryEventMapper.insert(event);
96-
} catch (SQLException e) {
97-
LOGGER.error("Error while inserting classification history event into database", e);
98-
} finally {
99-
taskanaHistoryEngine.returnConnection();
113+
114+
if (event.getCreated() == null) {
115+
Instant now = Instant.now();
116+
event.setCreated(now);
100117
}
118+
classificationHistoryEventMapper.insert(event);
101119
}
102120

103121
@Override

history/taskana-simplehistory-provider/src/main/java/pro/taskana/simplehistory/impl/TaskanaHistoryEngineImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ public class TaskanaHistoryEngineImpl implements TaskanaHistoryEngine {
5555
protected TaskanaHistoryEngineImpl(TaskanaEngine taskanaEngine) {
5656
this.taskanaConfiguration = taskanaEngine.getConfiguration();
5757
this.taskanaEngine = taskanaEngine;
58-
5958
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
6059
sessionManager = createSqlSessionManager();
6160
}

history/taskana-simplehistory-provider/src/test/java/pro/taskana/simplehistory/impl/SimpleHistoryServiceImplTest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ void should_VerifyMethodInvocations_When_CreateTaskHistoryEvent() throws Excepti
5858
"wbKey1", "taskId1", "type1", "wbKey2", "someUserId", "someDetails");
5959

6060
cutSpy.create(expectedWb);
61-
verify(taskanaHistoryEngineMock, times(1)).openConnection();
6261
verify(taskHistoryEventMapperMock, times(1)).insert(expectedWb);
63-
verify(taskanaHistoryEngineMock, times(1)).returnConnection();
6462
assertThat(expectedWb.getCreated()).isNotNull();
6563
}
6664

@@ -71,9 +69,7 @@ void should_VerifyMethodInvocations_When_CreateWorkbasketHisoryEvent() throws Ex
7169
"wbKey1", WorkbasketHistoryEventType.CREATED.getName(), "someUserId", "someDetails");
7270

7371
cutSpy.create(expectedEvent);
74-
verify(taskanaHistoryEngineMock, times(1)).openConnection();
7572
verify(workbasketHistoryEventMapperMock, times(1)).insert(expectedEvent);
76-
verify(taskanaHistoryEngineMock, times(1)).returnConnection();
7773
assertThat(expectedEvent.getCreated()).isNotNull();
7874
}
7975

history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryEventController.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package pro.taskana.simplehistory.rest;
22

33
import java.beans.ConstructorProperties;
4-
import java.sql.SQLException;
54
import java.util.List;
65
import java.util.function.BiConsumer;
76
import javax.servlet.http.HttpServletRequest;
@@ -14,7 +13,6 @@
1413
import org.springframework.web.bind.annotation.GetMapping;
1514
import org.springframework.web.bind.annotation.PathVariable;
1615
import org.springframework.web.bind.annotation.RestController;
17-
import pro.taskana.TaskanaConfiguration;
1816
import pro.taskana.common.api.BaseQuery.SortDirection;
1917
import pro.taskana.common.api.TaskanaEngine;
2018
import pro.taskana.common.api.exceptions.InvalidArgumentException;
@@ -35,19 +33,17 @@
3533
@RestController
3634
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
3735
public class TaskHistoryEventController {
38-
3936
private final SimpleHistoryServiceImpl simpleHistoryService;
4037
private final TaskHistoryEventRepresentationModelAssembler assembler;
4138

4239
@Autowired
4340
public TaskHistoryEventController(
44-
TaskanaConfiguration taskanaConfiguration,
41+
TaskanaEngine taskanaEngine,
4542
SimpleHistoryServiceImpl simpleHistoryServiceImpl,
46-
TaskHistoryEventRepresentationModelAssembler assembler)
47-
throws SQLException {
43+
TaskHistoryEventRepresentationModelAssembler assembler) {
4844

4945
this.simpleHistoryService = simpleHistoryServiceImpl;
50-
this.simpleHistoryService.initialize(TaskanaEngine.buildTaskanaEngine(taskanaConfiguration));
46+
this.simpleHistoryService.initialize(taskanaEngine);
5147
this.assembler = assembler;
5248
}
5349

lib/taskana-core/src/main/java/pro/taskana/common/api/TaskanaEngine.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.sql.SQLException;
44
import java.util.function.Supplier;
5+
import org.apache.ibatis.transaction.TransactionFactory;
56
import pro.taskana.TaskanaConfiguration;
67
import pro.taskana.classification.api.ClassificationService;
78
import pro.taskana.common.api.exceptions.NotAuthorizedException;
@@ -93,7 +94,7 @@ public interface TaskanaEngine {
9394
*/
9495
@SuppressWarnings("checkstyle:JavadocMethod")
9596
static TaskanaEngine buildTaskanaEngine(TaskanaConfiguration configuration) throws SQLException {
96-
return buildTaskanaEngine(configuration, ConnectionManagementMode.PARTICIPATE);
97+
return buildTaskanaEngine(configuration, ConnectionManagementMode.PARTICIPATE, null);
9798
}
9899

99100
/**
@@ -108,7 +109,26 @@ static TaskanaEngine buildTaskanaEngine(TaskanaConfiguration configuration) thro
108109
static TaskanaEngine buildTaskanaEngine(
109110
TaskanaConfiguration configuration, ConnectionManagementMode connectionManagementMode)
110111
throws SQLException {
111-
return TaskanaEngineImpl.createTaskanaEngine(configuration, connectionManagementMode);
112+
return buildTaskanaEngine(configuration, connectionManagementMode, null);
113+
}
114+
115+
/**
116+
* Builds an {@linkplain TaskanaEngine} based on {@linkplain TaskanaConfiguration},
117+
* SqlConnectionMode and TransactionFactory.
118+
*
119+
* @param configuration complete taskanaConfig to build the engine
120+
* @param connectionManagementMode connectionMode for the SqlSession
121+
* @param transactionFactory the TransactionFactory
122+
* @return a {@linkplain TaskanaEngineImpl}
123+
* @throws SQLException when the db schema could not be initialized
124+
*/
125+
static TaskanaEngine buildTaskanaEngine(
126+
TaskanaConfiguration configuration,
127+
ConnectionManagementMode connectionManagementMode,
128+
TransactionFactory transactionFactory)
129+
throws SQLException {
130+
return TaskanaEngineImpl.createTaskanaEngine(
131+
configuration, connectionManagementMode, transactionFactory);
112132
}
113133

114134
/**

lib/taskana-core/src/main/java/pro/taskana/common/internal/TaskanaEngineImpl.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ public class TaskanaEngineImpl implements TaskanaEngine {
112112
protected Connection connection;
113113

114114
protected TaskanaEngineImpl(
115-
TaskanaConfiguration taskanaConfiguration, ConnectionManagementMode connectionManagementMode)
115+
TaskanaConfiguration taskanaConfiguration,
116+
ConnectionManagementMode connectionManagementMode,
117+
TransactionFactory transactionFactory)
116118
throws SQLException {
117119
LOGGER.info(
118120
"initializing TASKANA with this configuration: {} and this mode: {}",
@@ -146,7 +148,11 @@ protected TaskanaEngineImpl(
146148

147149
currentUserContext =
148150
new CurrentUserContextImpl(TaskanaConfiguration.shouldUseLowerCaseForAccessIds());
149-
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
151+
if (transactionFactory == null) {
152+
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
153+
} else {
154+
this.transactionFactory = transactionFactory;
155+
}
150156
sessionManager = createSqlSessionManager();
151157

152158
initializeDbSchema(taskanaConfiguration);
@@ -156,7 +162,8 @@ protected TaskanaEngineImpl(
156162
new TaskanaConfiguration.Builder(this.taskanaConfiguration)
157163
.jobSchedulerEnabled(false)
158164
.build();
159-
TaskanaEngine taskanaEngine = TaskanaEngine.buildTaskanaEngine(configuration, EXPLICIT);
165+
TaskanaEngine taskanaEngine =
166+
TaskanaEngine.buildTaskanaEngine(configuration, EXPLICIT, transactionFactory);
160167
RealClock clock =
161168
new RealClock(
162169
this.taskanaConfiguration.getJobSchedulerInitialStartDelay(),
@@ -186,9 +193,12 @@ protected TaskanaEngineImpl(
186193
}
187194

188195
public static TaskanaEngine createTaskanaEngine(
189-
TaskanaConfiguration taskanaConfiguration, ConnectionManagementMode connectionManagementMode)
196+
TaskanaConfiguration taskanaConfiguration,
197+
ConnectionManagementMode connectionManagementMode,
198+
TransactionFactory transactionFactory)
190199
throws SQLException {
191-
return new TaskanaEngineImpl(taskanaConfiguration, connectionManagementMode);
200+
return new TaskanaEngineImpl(
201+
taskanaConfiguration, connectionManagementMode, transactionFactory);
192202
}
193203

194204
@Override
@@ -246,7 +256,11 @@ public void setConnection(Connection connection) throws SQLException {
246256
connection.setAutoCommit(false);
247257
connection.setSchema(taskanaConfiguration.getSchemaName());
248258
mode = EXPLICIT;
249-
sessionManager.startManagedSession(connection);
259+
if (transactionFactory.getClass().getSimpleName().equals("SpringManagedTransactionFactory")) {
260+
sessionManager.startManagedSession();
261+
} else {
262+
sessionManager.startManagedSession(connection);
263+
}
250264
} else if (this.connection != null) {
251265
closeConnection();
252266
}

lib/taskana-spring/src/main/java/pro/taskana/common/internal/SpringTaskanaEngineImpl.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ public class SpringTaskanaEngineImpl extends TaskanaEngineImpl implements Spring
1010
public SpringTaskanaEngineImpl(
1111
TaskanaConfiguration taskanaConfiguration, ConnectionManagementMode mode)
1212
throws SQLException {
13-
super(taskanaConfiguration, mode);
14-
this.transactionFactory = new SpringManagedTransactionFactory();
15-
this.sessionManager = createSqlSessionManager();
13+
super(taskanaConfiguration, mode, new SpringManagedTransactionFactory());
1614
}
1715

1816
public static SpringTaskanaEngine createTaskanaEngine(

rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/ExampleRestConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.springframework.transaction.PlatformTransactionManager;
1212
import pro.taskana.TaskanaConfiguration;
1313
import pro.taskana.common.api.TaskanaEngine;
14+
import pro.taskana.common.internal.SpringTaskanaEngine;
1415
import pro.taskana.common.internal.configuration.DbSchemaCreator;
1516
import pro.taskana.sampledata.SampleDataGenerator;
1617

@@ -44,7 +45,7 @@ public SampleDataGenerator generateSampleData(
4445
@DependsOn("generateSampleData")
4546
public TaskanaEngine getTaskanaEngine(TaskanaConfiguration taskanaConfiguration)
4647
throws SQLException {
47-
return TaskanaEngine.buildTaskanaEngine(taskanaConfiguration);
48+
return SpringTaskanaEngine.buildTaskanaEngine(taskanaConfiguration);
4849
}
4950

5051
// only required to let the adapter example connect to the same database

0 commit comments

Comments
 (0)