fix: schedule manual outbox retries in UTC
This commit is contained in:
@@ -7,6 +7,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -76,7 +77,8 @@ public final class OutboxOperations {
|
||||
return result(canonicalId, identity, request, true);
|
||||
}
|
||||
if (!"DEAD".equals(row.get("entry_status"))) throw conflict("仅允许重试 DEAD 事件");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// Relay due-time comparisons use UTC, independently of the JVM default zone.
|
||||
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
||||
int updated = jdbc.update("UPDATE " + layout.table() + " SET " + layout.statusColumn()
|
||||
+ "='PENDING',retry_count=0,next_retry_at=?,locked_by=NULL,locked_at=NULL,last_error=NULL WHERE "
|
||||
+ layout.idColumn() + "=? AND " + layout.statusColumn() + "='DEAD'", now, canonicalId);
|
||||
|
||||
@@ -5,15 +5,20 @@ import com.baiyue.common.core.outbox.OutboxOperations;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Isolated;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@Isolated("UTC retry regression temporarily changes the JVM default time zone")
|
||||
class OutboxOperationsTest {
|
||||
private static final String HASH = "sha256:" + "a".repeat(64);
|
||||
private JdbcTemplate jdbc;
|
||||
@@ -54,6 +59,35 @@ class OutboxOperationsTest {
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM event_delivery_retry_audit", Integer.class)).isZero();
|
||||
}
|
||||
|
||||
@Test void retryIsImmediatelyDueInUtcUnderShanghaiDefaultZoneAndReplayKeepsTimes() {
|
||||
TimeZone original = TimeZone.getDefault();
|
||||
try {
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
var request = request();
|
||||
LocalDateTime before = LocalDateTime.now(ZoneOffset.UTC).minusSeconds(1);
|
||||
assertThat(operations.retry("1", request).duplicate()).isFalse();
|
||||
LocalDateTime nextRetry = jdbc.queryForObject("SELECT next_retry_at FROM events WHERE id='1'",
|
||||
(row, index) -> row.getObject(1, LocalDateTime.class));
|
||||
LocalDateTime requested = jdbc.queryForObject("SELECT requested_at FROM event_delivery_retry_audit",
|
||||
(row, index) -> row.getObject(1, LocalDateTime.class));
|
||||
LocalDateTime scan = LocalDateTime.now(ZoneOffset.UTC);
|
||||
// Allow database timestamp precision rounding, but never a default-zone offset.
|
||||
assertThat(nextRetry).isBetween(before, scan.plusSeconds(1));
|
||||
assertThat(requested).isEqualTo(nextRetry);
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM events WHERE id='1' AND status='PENDING' AND next_retry_at<=?",
|
||||
Integer.class, scan)).isEqualTo(1);
|
||||
assertThat(operations.retry("1", request).duplicate()).isTrue();
|
||||
assertThat(jdbc.<LocalDateTime>queryForObject("SELECT next_retry_at FROM events WHERE id='1'",
|
||||
(row, index) -> row.getObject(1, LocalDateTime.class))).isEqualTo(nextRetry);
|
||||
assertThat(jdbc.<LocalDateTime>queryForObject("SELECT requested_at FROM event_delivery_retry_audit",
|
||||
(row, index) -> row.getObject(1, LocalDateTime.class))).isEqualTo(requested);
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM event_delivery_retry_audit", Integer.class)).isEqualTo(1);
|
||||
assertThat(jdbc.queryForObject("SELECT payload_json FROM events WHERE id='1'", String.class)).isEqualTo("event-1");
|
||||
} finally {
|
||||
TimeZone.setDefault(original);
|
||||
}
|
||||
}
|
||||
|
||||
@Test void staleHashAndNonDeadAndReusedRequestAreRejected() {
|
||||
var original = request();
|
||||
var stale = new OutboxOperations.RetryRequest(original.requestId(), original.operator(), original.reason(), "sha256:" + "b".repeat(64));
|
||||
|
||||
Reference in New Issue
Block a user