Skip to content

Commit

Permalink
prevent mssqlserver deadlock
Browse files Browse the repository at this point in the history
Signed-off-by: arnaud.mergey@semarchy.com <arnaud.mergey@semarchy.com>
  • Loading branch information
amergey committed Oct 31, 2024
1 parent 19e622c commit 4beb4de
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 0 deletions.
2 changes: 2 additions & 0 deletions quartz/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-params'
testImplementation 'org.junit.jupiter:junit-jupiter-params'
testImplementation 'org.mockito:mockito-core:5.14.2'
testImplementation 'org.testcontainers:mssqlserver:1.20.3'
testImplementation 'com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11'
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,20 @@

package org.quartz.impl.jdbcjobstore;

import static org.quartz.TriggerKey.triggerKey;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;

import org.quartz.TriggerKey;

/**
* <p>
Expand Down Expand Up @@ -80,6 +89,44 @@ protected Object getJobDataFromBlob(ResultSet rs, String colName)
}
return getObjectFromBlob(rs, colName);
}

@Override
public List<TriggerKey> selectTriggerToAcquire(Connection conn, long noLaterThan, long noEarlierThan, int maxCount)
throws SQLException {
// Set max rows to retrieve
if (maxCount < 1)
maxCount = 1; // we want at least one trigger back.
String selectTriggerToAcquire = "SELECT TOP " + maxCount + " " + SELECT_NEXT_TRIGGER_TO_ACQUIRE.substring(6);
PreparedStatement ps = null;
ResultSet rs = null;
List<TriggerKey> nextTriggers = new LinkedList<>();
try {
ps = conn.prepareStatement(rtp(selectTriggerToAcquire));

ps.setMaxRows(maxCount);

// Try to give jdbc driver a hint to hopefully not pull over more than the few rows we actually need.
// Note: in some jdbc drivers, such as MySQL, you must set maxRows before fetchSize, or you get exception!
ps.setFetchSize(maxCount);

ps.setString(1, STATE_WAITING);
ps.setBigDecimal(2, new BigDecimal(String.valueOf(noLaterThan)));
ps.setBigDecimal(3, new BigDecimal(String.valueOf(noEarlierThan)));
rs = ps.executeQuery();

while (rs.next() && nextTriggers.size() < maxCount) {
nextTriggers.add(triggerKey(
rs.getString(COL_TRIGGER_NAME),
rs.getString(COL_TRIGGER_GROUP)));
}

return nextTriggers;
} finally {
closeResultSet(rs);
closeStatement(ps);
}
}

}

// EOF
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright Super iPaaS Integration LLC, an IBM Company 2024
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.quartz.impl.jdbcjobstore;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.testcontainers.containers.MSSQLServerContainer;

/**
* A utility class to create a database for Quartz MSSQL test.
*
* @author Arnaud Mergey
*/

public final class JdbcQuartzMSSQLUtilities {
private static final List<String> DATABASE_SETUP_STATEMENTS;
static {
List<String> setup = new ArrayList<String>();
String setupScript;
try {
InputStream setupStream = MSSQLDelegate.class.getClassLoader()
.getResourceAsStream("org/quartz/impl/jdbcjobstore/tables_sqlServer.sql");
try {
BufferedReader r = new BufferedReader(new InputStreamReader(setupStream, "US-ASCII"));
StringBuilder sb = new StringBuilder();
while (true) {
String line = r.readLine();
if (line == null) {
break;
} else if (!line.startsWith("--")) {
sb.append(line.replace("GO", ";").replace("[enter_db_name_here]", "[master]")).append("\n");
}
}
setupScript = sb.toString();
} finally {
setupStream.close();
}
} catch (IOException e) {
throw new AssertionError(e);
}
for (String command : setupScript.split(";")) {
if (!command.matches("\\s*")) {
setup.add(command);
}
}
DATABASE_SETUP_STATEMENTS = setup;
}

public static void createDatabase(MSSQLServerContainer<?> container) throws SQLException {
Connection conn = container.createConnection("");
try {
Statement statement = conn.createStatement();
for (String command : DATABASE_SETUP_STATEMENTS) {
statement.addBatch(command);
}
statement.executeBatch();
} finally {
conn.close();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright Super iPaaS Integration LLC, an IBM Company 2024
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.quartz.integrations.tests;
import java.util.Collections;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.Test;
import org.quartz.*;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
import static org.quartz.integrations.tests.TrackingJob.SCHEDULED_TIMES_KEY;
/**
* A integration test for Quartz MSSQL Database Scheduler with Cron Trigger.
*
* @author Arnaud Mergey
*/
public class QuartzMSSQLDatabaseCronTriggerTest extends QuartzMSSQLTestSupport {
@Test
void testCronRepeatCount() throws Exception {
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("test")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
.build();
List<Long> scheduledTimes = Collections.synchronizedList(new LinkedList<Long>());
scheduler.getContext().put(SCHEDULED_TIMES_KEY, scheduledTimes);
JobDetail jobDetail = JobBuilder.newJob(TrackingJob.class).withIdentity("test").build();
scheduler.scheduleJob(jobDetail, trigger);

for (int i = 0; i < 20 && scheduledTimes.size() < 3; i++) {
Thread.sleep(500);
}
assertThat(scheduledTimes, hasSize(greaterThanOrEqualTo(3)));

Long[] times = scheduledTimes.toArray(new Long[scheduledTimes.size()]);
long baseline = times[0];
assertThat(baseline % 1000, is(0L));
for (int i = 1; i < times.length; i++) {
assertThat(times[i], is(baseline + TimeUnit.SECONDS.toMillis(i)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright Super iPaaS Integration LLC, an IBM Company 2024
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.quartz.integrations.tests;

import java.sql.SQLException;
import java.util.Properties;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.quartz.impl.jdbcjobstore.JdbcQuartzMSSQLUtilities;
import org.quartz.impl.jdbcjobstore.MSSQLDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.utility.DockerImageName;

/**
* A base class to support database (MSSQL) scheduler integration testing. Each
* test will have a fresh scheduler created and started, and it will auto
* shutdown upon each test run. The database will be created with schema before
* class and destroy after class test.
*
* @author Arnaud Mergey
*/
public class QuartzMSSQLTestSupport extends QuartzMemoryTestSupport {
protected static final Logger LOG = LoggerFactory.getLogger(QuartzMSSQLTestSupport.class);
static MSSQLServerContainer<?> mssqlserver = new MSSQLServerContainer(
DockerImageName.parse(MSSQLServerContainer.IMAGE).withTag("latest")).acceptLicense();

@BeforeAll
public static void initialize() throws Exception {
LOG.info("Starting MSSQL database.");
mssqlserver.start();
LOG.info("Database started.");
try {
LOG.info("Creating Database tables for Quartz.");
JdbcQuartzMSSQLUtilities.createDatabase(mssqlserver);
LOG.info("Database tables created.");
} catch (SQLException e) {
throw new Exception("Failed to create Quartz tables.", e);
}
}

@AfterAll
public static void shutdownDb() throws Exception {
mssqlserver.stop();
LOG.info("Database shutdown.");
}

protected Properties createSchedulerProperties() {
Properties properties = new Properties();
properties.put("org.quartz.scheduler.instanceName", "TestScheduler");
properties.put("org.quartz.scheduler.instanceId", "AUTO");
properties.put("org.quartz.scheduler.skipUpdateCheck", "true");
properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
properties.put("org.quartz.threadPool.threadCount", "12");
properties.put("org.quartz.threadPool.threadPriority", "5");
properties.put("org.quartz.jobStore.misfireThreshold", "10000");
properties.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
properties.put("org.quartz.jobStore.driverDelegateClass", MSSQLDelegate.class.getName());
properties.put("org.quartz.jobStore.useProperties", "true");
properties.put("org.quartz.jobStore.dataSource", "myDS");
properties.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
properties.put("org.quartz.jobStore.isClustered", "false");
properties.put("org.quartz.dataSource.myDS.driver", mssqlserver.getDriverClassName());
properties.put("org.quartz.dataSource.myDS.URL", mssqlserver.getJdbcUrl());
properties.put("org.quartz.dataSource.myDS.user", mssqlserver.getUsername());
properties.put("org.quartz.dataSource.myDS.password", mssqlserver.getPassword());
properties.put("org.quartz.dataSource.myDS.maxConnections", "5");
properties.put("org.quartz.dataSource.myDS.provider", "hikaricp");
return properties;
}
}

0 comments on commit 4beb4de

Please sign in to comment.