From ec6ef6fefc8e8ebcd5dddf5871cbd4dc04d3f2ee Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:03:04 -0700 Subject: [PATCH 1/5] Allow re-scheduleSnapshotAutoUpdate --- .github/workflows/master-2.yml | 4 ++-- .github/workflows/master.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- .github/workflows/sonar.yml | 2 +- .github/workflows/staging.yml | 2 +- .../switcherapi/client/SwitcherContextBase.java | 6 +++++- .../client/SwitcherSnapshotAutoUpdateTest.java | 11 ++++++++--- .../switcherapi/client/utils/SnapshotTest.java | 17 +++++++++++++++++ .../client/utils/SnapshotWatcherWorkerTest.java | 7 +++---- 9 files changed, 41 insertions(+), 16 deletions(-) diff --git a/.github/workflows/master-2.yml b/.github/workflows/master-2.yml index 1f64f7f..b94ab7f 100644 --- a/.github/workflows/master-2.yml +++ b/.github/workflows/master-2.yml @@ -9,7 +9,7 @@ on: jobs: build-scan: name: SonarCloud Scan - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: java: ['11', '17', '21'] - os: [ubuntu-22.04, windows-latest] + os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 1e282a1..c8000c7 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -9,7 +9,7 @@ on: jobs: build-scan: name: SonarCloud Scan - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: java: ['8', '11', '17', '21'] - os: [ubuntu-22.04, windows-latest] + os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0dc27d..0ee87e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: java: ['11', '17', '21'] - os: [ubuntu-22.04, windows-latest] + os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -39,7 +39,7 @@ jobs: publish: name: Publish Release needs: [build-test] - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 2f67530..db54d6e 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -11,7 +11,7 @@ on: jobs: sonar-analysis: name: SonarCloud Analysis for PR - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Get PR details diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index a48d1fd..7237769 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -9,7 +9,7 @@ on: required: true default: '17' os: - description: 'Operating System (ubuntu-22.04, ubuntu-latest, windows-latest)' + description: 'Operating System (ubuntu-latest, windows-latest)' required: true default: 'ubuntu-latest' diff --git a/src/main/java/com/switcherapi/client/SwitcherContextBase.java b/src/main/java/com/switcherapi/client/SwitcherContextBase.java index 3d92352..de7eaba 100644 --- a/src/main/java/com/switcherapi/client/SwitcherContextBase.java +++ b/src/main/java/com/switcherapi/client/SwitcherContextBase.java @@ -292,10 +292,14 @@ private static void scheduleSnapshotWatcher() { * @return ScheduledFuture instance */ public static ScheduledFuture scheduleSnapshotAutoUpdate(String intervalValue, SnapshotCallback callback) { - if (StringUtils.isBlank(intervalValue) || scheduledExecutorService != null) { + if (StringUtils.isBlank(intervalValue)) { return null; } + if (Objects.nonNull(scheduledExecutorService)) { + terminateSnapshotAutoUpdateWorker(); + } + final long interval = SwitcherUtils.getMillis(intervalValue); final SnapshotCallback callbackFinal = Optional.ofNullable(callback).orElse(new SnapshotCallback() {}); final Runnable runnableSnapshotValidate = () -> { diff --git a/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java b/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java index af23c06..991a7da 100644 --- a/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java +++ b/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java @@ -219,11 +219,15 @@ void shouldNotKillThread_whenAPI_wentLocal() { @Test @Order(6) - void shouldPreventSnapshotAutoUpdateToStart_whenAlreadySetup() { - //given + void shouldRestartSnapshotAutoUpdate_whenAlreadySetup() { + //given - initialize (snapshot autoload) givenResponse(generateMockAuth(10)); //auth givenResponse(generateSnapshotResponse("default.json", SNAPSHOTS_LOCAL)); //graphql + //given - snapshot auto update + givenResponse(generateCheckSnapshotVersionResponse(Boolean.toString(false))); //criteria/snapshot_check + givenResponse(generateSnapshotResponse("default.json", SNAPSHOTS_LOCAL)); //graphql + //that Switchers.configure(ContextBuilder.builder(true) .context(Switchers.class.getCanonicalName()) @@ -235,8 +239,9 @@ void shouldPreventSnapshotAutoUpdateToStart_whenAlreadySetup() { .snapshotAutoUpdateInterval("1s")); Switchers.initializeClient(); + ScheduledFuture snapshotUpdater = Switchers.scheduleSnapshotAutoUpdate("1m"); - assertNull(snapshotUpdater); + assertNotNull(snapshotUpdater); } } diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotTest.java index 8ef99d0..e1eb079 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotTest.java @@ -7,6 +7,7 @@ import com.switcherapi.client.service.WorkerName; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.switcherapi.fixture.CountDownHelper; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,4 +75,20 @@ protected void assertWorker(boolean exists) { assertEquals(exists, Thread.getAllStackTraces().keySet().stream() .anyMatch(t -> t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString()))); } + + protected void assertWorkerUntil(boolean exists, long seconds) { + long count = 0; + while (count < seconds) { + if (Thread.getAllStackTraces().keySet().stream() + .anyMatch(t -> t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) == exists) { + break; + } + + CountDownHelper.wait(1); + count++; + } + + assertEquals(exists, Thread.getAllStackTraces().keySet().stream() + .anyMatch(t -> t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString()))); + } } diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java index 011afb3..ba769f9 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java @@ -22,12 +22,11 @@ static void setupContext() { @Test void shouldStartAndKillWorker() { SwitchersBase.watchSnapshot(); - assertWorker(true); + CountDownHelper.wait(1); + assertWorkerUntil(true, 2); SwitchersBase.stopWatchingSnapshot(); - CountDownHelper.wait(2); - - assertWorker(false); + assertWorkerUntil(false, 15); } } From 62b295cd7ddf7769513efaaccbb28acae2018783 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:14:31 -0700 Subject: [PATCH 2/5] debug: dangling thread after termination --- .../utils/SnapshotWatcherWorkerTest.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java index ba769f9..b15e589 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java @@ -2,6 +2,7 @@ import com.switcherapi.SwitchersBase; import com.switcherapi.client.ContextBuilder; +import com.switcherapi.client.service.WorkerName; import com.switcherapi.fixture.CountDownHelper; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -25,8 +26,24 @@ void shouldStartAndKillWorker() { CountDownHelper.wait(1); assertWorkerUntil(true, 2); + Thread.getAllStackTraces().keySet() + .forEach(t -> { + if (t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) { + System.out.println("Thread: " + t.getName() + " - State: " + t.getState()); + } + }); + SwitchersBase.stopWatchingSnapshot(); - assertWorkerUntil(false, 15); + CountDownHelper.wait(2); + + Thread.getAllStackTraces().keySet() + .forEach(t -> { + if (t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) { + System.out.println("Thread: " + t.getName() + " - State: " + t.getState()); + } + }); + + assertWorkerUntil(false, 10); } } From 7fad08139b574a2492521b9f0a34b187b4159b63 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:21:50 -0700 Subject: [PATCH 3/5] test: improved tearDown logic for watcher schedulers --- .../client/utils/SnapshotWatcherTest.java | 15 ++++++--------- .../client/utils/SnapshotWatcherWorkerTest.java | 4 ++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java index 7a04c6a..e078711 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java @@ -4,10 +4,7 @@ import com.switcherapi.client.ContextBuilder; import com.switcherapi.client.model.SwitcherRequest; import com.switcherapi.fixture.CountDownHelper; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import java.io.IOException; @@ -29,17 +26,17 @@ static void setupContext() throws IOException { SwitchersBase.initializeClient(); } - - @AfterAll - static void tearDown() { - SwitchersBase.stopWatchingSnapshot(); - } @BeforeEach void prepareTest() { generateFixture(); SwitchersBase.watchSnapshot(); } + + @AfterEach + void cleanUp() { + SwitchersBase.stopWatchingSnapshot(); + } @Test void shouldNotReloadDomainAfterChangingSnapshot() { diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java index b15e589..f2b9a3d 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java @@ -29,7 +29,7 @@ void shouldStartAndKillWorker() { Thread.getAllStackTraces().keySet() .forEach(t -> { if (t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) { - System.out.println("Thread: " + t.getName() + " - State: " + t.getState()); + System.out.println("Thread: " + t.getName() + " - Alive: " + t.isAlive()); } }); @@ -39,7 +39,7 @@ void shouldStartAndKillWorker() { Thread.getAllStackTraces().keySet() .forEach(t -> { if (t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) { - System.out.println("Thread: " + t.getName() + " - State: " + t.getState()); + System.out.println("Thread: " + t.getName() + " - Alive: " + t.isAlive()); } }); From 09d91a278f78773aaf5555257e9a53e7b1b46972 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:34:27 -0700 Subject: [PATCH 4/5] debug: dangling thread after termination --- .../switcherapi/client/SwitcherContextBase.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/switcherapi/client/SwitcherContextBase.java b/src/main/java/com/switcherapi/client/SwitcherContextBase.java index de7eaba..f757057 100644 --- a/src/main/java/com/switcherapi/client/SwitcherContextBase.java +++ b/src/main/java/com/switcherapi/client/SwitcherContextBase.java @@ -461,9 +461,18 @@ public static void watchSnapshot(SnapshotEventHandler handler) { */ public static void stopWatchingSnapshot() { if (Objects.nonNull(watcherSnapshot)) { - watcherExecutorService.shutdownNow(); - watcherSnapshot.terminate(); - watcherSnapshot = null; + watcherExecutorService.shutdown(); + try { + if (!watcherExecutorService.awaitTermination(5, TimeUnit.SECONDS)) { + watcherExecutorService.shutdownNow(); + } + } catch (InterruptedException e) { + watcherExecutorService.shutdownNow(); + Thread.currentThread().interrupt(); + } finally { + watcherSnapshot.terminate(); + watcherSnapshot = null; + } } } From ff6bdece314eca5acc4f1e867d5f8fb52712584b Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:40:30 -0700 Subject: [PATCH 5/5] revert: ubuntu 24.04 failing to terminate schedulers during test --- .github/workflows/master-2.yml | 4 ++-- .github/workflows/master.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- .github/workflows/sonar.yml | 2 +- .../client/SwitcherContextBase.java | 15 +++----------- .../client/utils/SnapshotTest.java | 17 ---------------- .../client/utils/SnapshotWatcherTest.java | 15 ++++++++------ .../utils/SnapshotWatcherWorkerTest.java | 20 ++----------------- 8 files changed, 21 insertions(+), 60 deletions(-) diff --git a/.github/workflows/master-2.yml b/.github/workflows/master-2.yml index b94ab7f..1f64f7f 100644 --- a/.github/workflows/master-2.yml +++ b/.github/workflows/master-2.yml @@ -9,7 +9,7 @@ on: jobs: build-scan: name: SonarCloud Scan - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: java: ['11', '17', '21'] - os: [ubuntu-latest, windows-latest] + os: [ubuntu-22.04, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index c8000c7..1e282a1 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -9,7 +9,7 @@ on: jobs: build-scan: name: SonarCloud Scan - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: java: ['8', '11', '17', '21'] - os: [ubuntu-latest, windows-latest] + os: [ubuntu-22.04, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ee87e7..f0dc27d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: java: ['11', '17', '21'] - os: [ubuntu-latest, windows-latest] + os: [ubuntu-22.04, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -39,7 +39,7 @@ jobs: publish: name: Publish Release needs: [build-test] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index db54d6e..2f67530 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -11,7 +11,7 @@ on: jobs: sonar-analysis: name: SonarCloud Analysis for PR - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Get PR details diff --git a/src/main/java/com/switcherapi/client/SwitcherContextBase.java b/src/main/java/com/switcherapi/client/SwitcherContextBase.java index f757057..de7eaba 100644 --- a/src/main/java/com/switcherapi/client/SwitcherContextBase.java +++ b/src/main/java/com/switcherapi/client/SwitcherContextBase.java @@ -461,18 +461,9 @@ public static void watchSnapshot(SnapshotEventHandler handler) { */ public static void stopWatchingSnapshot() { if (Objects.nonNull(watcherSnapshot)) { - watcherExecutorService.shutdown(); - try { - if (!watcherExecutorService.awaitTermination(5, TimeUnit.SECONDS)) { - watcherExecutorService.shutdownNow(); - } - } catch (InterruptedException e) { - watcherExecutorService.shutdownNow(); - Thread.currentThread().interrupt(); - } finally { - watcherSnapshot.terminate(); - watcherSnapshot = null; - } + watcherExecutorService.shutdownNow(); + watcherSnapshot.terminate(); + watcherSnapshot = null; } } diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotTest.java index e1eb079..8ef99d0 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotTest.java @@ -7,7 +7,6 @@ import com.switcherapi.client.service.WorkerName; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.switcherapi.fixture.CountDownHelper; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,20 +74,4 @@ protected void assertWorker(boolean exists) { assertEquals(exists, Thread.getAllStackTraces().keySet().stream() .anyMatch(t -> t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString()))); } - - protected void assertWorkerUntil(boolean exists, long seconds) { - long count = 0; - while (count < seconds) { - if (Thread.getAllStackTraces().keySet().stream() - .anyMatch(t -> t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) == exists) { - break; - } - - CountDownHelper.wait(1); - count++; - } - - assertEquals(exists, Thread.getAllStackTraces().keySet().stream() - .anyMatch(t -> t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString()))); - } } diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java index e078711..7a04c6a 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java @@ -4,7 +4,10 @@ import com.switcherapi.client.ContextBuilder; import com.switcherapi.client.model.SwitcherRequest; import com.switcherapi.fixture.CountDownHelper; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; @@ -26,17 +29,17 @@ static void setupContext() throws IOException { SwitchersBase.initializeClient(); } + + @AfterAll + static void tearDown() { + SwitchersBase.stopWatchingSnapshot(); + } @BeforeEach void prepareTest() { generateFixture(); SwitchersBase.watchSnapshot(); } - - @AfterEach - void cleanUp() { - SwitchersBase.stopWatchingSnapshot(); - } @Test void shouldNotReloadDomainAfterChangingSnapshot() { diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java index f2b9a3d..011afb3 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java @@ -2,7 +2,6 @@ import com.switcherapi.SwitchersBase; import com.switcherapi.client.ContextBuilder; -import com.switcherapi.client.service.WorkerName; import com.switcherapi.fixture.CountDownHelper; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -23,27 +22,12 @@ static void setupContext() { @Test void shouldStartAndKillWorker() { SwitchersBase.watchSnapshot(); - CountDownHelper.wait(1); - assertWorkerUntil(true, 2); - - Thread.getAllStackTraces().keySet() - .forEach(t -> { - if (t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) { - System.out.println("Thread: " + t.getName() + " - Alive: " + t.isAlive()); - } - }); + assertWorker(true); SwitchersBase.stopWatchingSnapshot(); CountDownHelper.wait(2); - Thread.getAllStackTraces().keySet() - .forEach(t -> { - if (t.getName().equals(WorkerName.SNAPSHOT_WATCH_WORKER.toString())) { - System.out.println("Thread: " + t.getName() + " - Alive: " + t.isAlive()); - } - }); - - assertWorkerUntil(false, 10); + assertWorker(false); } }