diff --git a/kraken-app/kraken-app-agent/src/main/resources/application.yaml b/kraken-app/kraken-app-agent/src/main/resources/application.yaml
index d3e4d79e..529d0ca8 100644
--- a/kraken-app/kraken-app-agent/src/main/resources/application.yaml
+++ b/kraken-app/kraken-app-agent/src/main/resources/application.yaml
@@ -103,6 +103,9 @@ app:
push-heartbeat: 0/20 * * * * *
push-log: 0/20 * * * * *
push-log-external-system: 0 0/1 * * * *
+ lock:
+ at-most-for: 2m
+ at-least-for: 10s
accept-asset-kinds:
- kraken.component.api
- kraken.component.api-target
diff --git a/kraken-java-sdk/kraken-java-sdk-data/src/main/java/com/consoleconnect/kraken/operator/data/entity/ShedLock.java b/kraken-java-sdk/kraken-java-sdk-data/src/main/java/com/consoleconnect/kraken/operator/data/entity/ShedLock.java
new file mode 100644
index 00000000..44ef1e63
--- /dev/null
+++ b/kraken-java-sdk/kraken-java-sdk-data/src/main/java/com/consoleconnect/kraken/operator/data/entity/ShedLock.java
@@ -0,0 +1,28 @@
+package com.consoleconnect.kraken.operator.data.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import java.time.ZonedDateTime;
+import lombok.Getter;
+import lombok.Setter;
+
+@Entity
+@Table(name = "kraken_shed_lock")
+@Getter
+@Setter
+public class ShedLock {
+ @Id
+ @Column(name = "name", length = 64, nullable = false)
+ private String name;
+
+ @Column(name = "lock_until", nullable = false)
+ private ZonedDateTime lockUntil;
+
+ @Column(name = "locked_at", nullable = false)
+ private ZonedDateTime lockedAt;
+
+ @Column(name = "locked_by", length = 255, nullable = false)
+ private String lockedBy;
+}
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/pom.xml b/kraken-java-sdk/kraken-java-sdk-sync/pom.xml
index f9369488..9fbd3c50 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/pom.xml
+++ b/kraken-java-sdk/kraken-java-sdk-sync/pom.xml
@@ -13,6 +13,16 @@
2.0.0-snapshot.0
+
+ net.javacrumbs.shedlock
+ shedlock-spring
+ 6.0.2
+
+
+ net.javacrumbs.shedlock
+ shedlock-provider-jdbc-template
+ 6.0.2
+
com.consoleconnect.kraken
kraken-java-sdk-data
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/config/SyncConfig.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/config/SyncConfig.java
index 0fa93b45..f8437041 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/config/SyncConfig.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/config/SyncConfig.java
@@ -1,13 +1,21 @@
package com.consoleconnect.kraken.operator.sync.config;
import com.consoleconnect.kraken.operator.sync.model.SyncProperty;
+import javax.sql.DataSource;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
+import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
+@EnableScheduling
+@EnableSchedulerLock(defaultLockAtMostFor = "${app.cron-job.lock.at-most-for}")
public class SyncConfig {
private static final int SIZE = 16 * 1024 * 1024;
@@ -17,6 +25,16 @@ public SyncProperty syncProperty() {
return new SyncProperty();
}
+ @Bean
+ public LockProvider lockProvider(DataSource dataSource) {
+ return new JdbcTemplateLockProvider(
+ JdbcTemplateLockProvider.Configuration.builder()
+ .withJdbcTemplate(new JdbcTemplate(dataSource))
+ .withTableName("kraken_shed_lock")
+ .usingDbTime()
+ .build());
+ }
+
@Bean
public WebClient webClient(SyncProperty syncProperty) {
ExchangeStrategies strategies =
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ApiServerSynchronizeService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ApiServerSynchronizeService.java
index 94e03194..b5e5b6da 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ApiServerSynchronizeService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ApiServerSynchronizeService.java
@@ -23,6 +23,7 @@
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ParameterizedTypeReference;
@@ -55,6 +56,10 @@ public ApiServerSynchronizeService(
this.applicationContext = applicationContext;
}
+ @SchedulerLock(
+ name = "synApiServerInfoLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.pull-api-server-info:-}")
public void synApiServerInfo() {
HttpResponse> res =
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/GeneralSyncService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/GeneralSyncService.java
index 5c29b6dd..09457a8b 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/GeneralSyncService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/GeneralSyncService.java
@@ -25,6 +25,7 @@
import java.util.Optional;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
@@ -61,6 +62,10 @@ public GeneralSyncService(
this.applicationContext = applicationContext;
}
+ @SchedulerLock(
+ name = "syncServerAssetsLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.pull-server-assets:-}")
public void syncServerAssets() {
// 1. Query from client db to determine the latest updated time
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/HeartBeatCollectorService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/HeartBeatCollectorService.java
index b856752c..221e4c84 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/HeartBeatCollectorService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/HeartBeatCollectorService.java
@@ -12,6 +12,7 @@
import com.consoleconnect.kraken.operator.core.toolkit.JsonToolkit;
import java.time.ZonedDateTime;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Scheduled;
@@ -31,6 +32,10 @@ public HeartBeatCollectorService(
this.environmentClientRepository = environmentClientRepository;
}
+ @SchedulerLock(
+ name = "heartBeatCollectorLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.push-heartbeat-collector:-}")
public void runIt() {
ZonedDateTime now = ZonedDateTime.now();
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/MgmtPullTemplateService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/MgmtPullTemplateService.java
index 447d5481..52ee0243 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/MgmtPullTemplateService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/MgmtPullTemplateService.java
@@ -23,6 +23,7 @@
import java.time.ZonedDateTime;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
@@ -55,6 +56,10 @@ public MgmtPullTemplateService(
this.eventSinkService = eventSinkService;
}
+ @SchedulerLock(
+ name = "pullMappingTemplateDetailsLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.download-mapping-template-content-from-mgmt:-}")
public void pullMappingTemplateDetails() {
Paging assetDtoPaging =
@@ -128,6 +133,10 @@ public void checkFirstStartUpAndExecuteInstalling(String id) {
installMappingTemplateViaMgmt(id);
}
+ @SchedulerLock(
+ name = "queryLatestProductReleaseLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.pull-latest-release-from-mgmt:-}")
public void queryLatestProductRelease() {
Paging assetDtoPaging =
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PullDeploymentService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PullDeploymentService.java
index dd4a5558..3ce3a68b 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PullDeploymentService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PullDeploymentService.java
@@ -20,6 +20,7 @@
import java.util.List;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.annotation.Scheduled;
@@ -100,6 +101,10 @@ protected void ingestData(UnifiedAssetDto dto) {
dataIngestionJob.ingestData(event);
}
+ @SchedulerLock(
+ name = "scheduledCheckLatestProductReleaseLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.pull-latest-release:-}")
public void scheduledCheckLatestProductRelease() {
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushHeartbeatService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushHeartbeatService.java
index 35a98525..60cc7992 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushHeartbeatService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushHeartbeatService.java
@@ -9,6 +9,7 @@
import java.time.ZonedDateTime;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
@@ -26,6 +27,10 @@ public PushHeartbeatService(
this.heartbeatRepository = heartbeatRepository;
}
+ @SchedulerLock(
+ name = "pushHeartbeatLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.push-heartbeat:-}")
public void runIt() {
ZonedDateTime now = ZonedDateTime.now();
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushKrakenVersionService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushKrakenVersionService.java
index 9e06658a..4cd7d5fa 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushKrakenVersionService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushKrakenVersionService.java
@@ -6,6 +6,7 @@
import com.consoleconnect.kraken.operator.core.repo.MgmtEventRepository;
import com.consoleconnect.kraken.operator.core.repo.SystemInfoRepository;
import lombok.RequiredArgsConstructor;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Scheduled;
@@ -20,6 +21,10 @@ public class PushKrakenVersionService {
private final MgmtEventRepository eventRepository;
private final MgmtEventRepository mgmtEventRepository;
+ @SchedulerLock(
+ name = "pushKrakenVersionLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.sync-system-info-from-control-plane:-}")
public void runIt() {
// produce sync kraken info event
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushLogService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushLogService.java
index 088843e4..da06e14d 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushLogService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushLogService.java
@@ -15,6 +15,7 @@
import java.time.ZonedDateTime;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@@ -34,6 +35,10 @@ public PushLogService(
this.apiActivityLogRepository = apiActivityLogRepository;
}
+ @SchedulerLock(
+ name = "pushLogLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.push-log:-}")
public void runIt() {
ZonedDateTime createdAt =
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushMgmtEventService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushMgmtEventService.java
index be201f47..4ee1dfd9 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushMgmtEventService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushMgmtEventService.java
@@ -15,6 +15,7 @@
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
@@ -50,6 +51,10 @@ public PushMgmtEventService(
this.eventSinkService = eventSinkService;
}
+ @SchedulerLock(
+ name = "pushMgmtEventLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.push-mgmt-event:-}")
public void pushMgmtEvent() {
List mgmtEventEntities =
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushRunningMapperInfoService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushRunningMapperInfoService.java
index 96177914..23349641 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushRunningMapperInfoService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/PushRunningMapperInfoService.java
@@ -15,6 +15,7 @@
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@@ -36,6 +37,10 @@ public PushRunningMapperInfoService(
}
@Transactional
+ @SchedulerLock(
+ name = "pushRunningMapperInfoLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.push-running-mapper:-}")
public void runIt() {
ZonedDateTime now = ZonedDateTime.now();
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ResetService.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ResetService.java
index 0b44e084..65aa7b04 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ResetService.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/ResetService.java
@@ -12,6 +12,7 @@
import com.consoleconnect.kraken.operator.sync.model.SyncProperty;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
@@ -35,6 +36,10 @@ public ResetService(
this.unifiedAssetService = unifiedAssetService;
}
+ @SchedulerLock(
+ name = "scanEventLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.pull-reset-event:-}")
public void scanEvent() {
// search event
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/push/PushAPIActivityLogScheduler.java b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/push/PushAPIActivityLogScheduler.java
index c070e80c..048d3100 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/push/PushAPIActivityLogScheduler.java
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/main/java/com/consoleconnect/kraken/operator/sync/service/push/PushAPIActivityLogScheduler.java
@@ -34,6 +34,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Page;
@@ -71,6 +72,10 @@ public PushAPIActivityLogScheduler(
this.apiActivityLogRepository = apiActivityLogRepository;
}
+ @SchedulerLock(
+ name = "pushApiActivityLogToExternalSystemLock",
+ lockAtMostFor = "${app.cron-job.lock.at-most-for}",
+ lockAtLeastFor = "${app.cron-job.lock.at-least-for}")
@Scheduled(cron = "${app.cron-job.push-log-external-system:-}")
List pushApiActivityLogToExternalSystem() {
Optional entity =
diff --git a/kraken-java-sdk/kraken-java-sdk-sync/src/test/resources/application.yaml b/kraken-java-sdk/kraken-java-sdk-sync/src/test/resources/application.yaml
index 03724933..13bd2157 100644
--- a/kraken-java-sdk/kraken-java-sdk-sync/src/test/resources/application.yaml
+++ b/kraken-java-sdk/kraken-java-sdk-sync/src/test/resources/application.yaml
@@ -92,3 +92,7 @@ app:
- kraken.component.api-target
task:
enabled: false
+ cron-job:
+ lock:
+ at-most-for: 0s
+ at-least-for: 0s