]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16374 Simplify purge scheduler
authorJacek <jacek.poreda@sonarsource.com>
Mon, 18 Jul 2022 15:05:02 +0000 (17:05 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 25 Jul 2022 20:03:57 +0000 (20:03 +0000)
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushModule.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializer.java [deleted file]
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeScheduler.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImpl.java [deleted file]
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializerTest.java [deleted file]
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImplTest.java [deleted file]
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerTest.java [new file with mode: 0644]

index f8f248abe6aeb5e5f3ed1d7c9fe56b0e7ec38aad..6c49bd7a6e9bb46c06f4d7f18c1388e215d93d30 100644 (file)
@@ -23,8 +23,7 @@ import org.sonar.core.platform.Module;
 import org.sonar.server.pushapi.scheduler.polling.PushEventPollExecutorServiceImpl;
 import org.sonar.server.pushapi.scheduler.polling.PushEventPollScheduler;
 import org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeExecutorServiceImpl;
-import org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeInitializer;
-import org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeSchedulerImpl;
+import org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler;
 import org.sonar.server.pushapi.sonarlint.SonarLintClientPermissionsValidator;
 import org.sonar.server.pushapi.sonarlint.SonarLintClientsRegistry;
 import org.sonar.server.pushapi.sonarlint.SonarLintPushAction;
@@ -42,8 +41,7 @@ public class ServerPushModule extends Module {
       PushEventPollExecutorServiceImpl.class,
       PushEventPollScheduler.class,
       // Push Events Purge
-      PushEventsPurgeSchedulerImpl.class,
-      PushEventsPurgeExecutorServiceImpl.class,
-      PushEventsPurgeInitializer.class);
+      PushEventsPurgeScheduler.class,
+      PushEventsPurgeExecutorServiceImpl.class);
   }
 }
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializer.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializer.java
deleted file mode 100644 (file)
index 1abea71..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.pushapi.scheduler.purge;
-
-import javax.annotation.Nullable;
-import org.sonar.api.platform.Server;
-import org.sonar.api.platform.ServerStartHandler;
-
-public class PushEventsPurgeInitializer implements ServerStartHandler {
-
-  private final PushEventsPurgeScheduler pushEventsPurgeScheduler;
-
-  public PushEventsPurgeInitializer(@Nullable PushEventsPurgeScheduler pushEventsPurgeScheduler) {
-    this.pushEventsPurgeScheduler = pushEventsPurgeScheduler;
-  }
-
-  @Override
-  public void onServerStart(Server server) {
-    if (pushEventsPurgeScheduler != null) {
-      pushEventsPurgeScheduler.startScheduling();
-    }
-  }
-
-}
index d6ce454f753e648da1dc33ee888527230be59fa2..1d6f6612ded2e8701b951dda0c831f7afcb3741d 100644 (file)
  */
 package org.sonar.server.pushapi.scheduler.purge;
 
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Set;
+import org.sonar.api.Startable;
+import org.sonar.api.config.Configuration;
 import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.util.GlobalLockManager;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
 
 @ServerSide
-public interface PushEventsPurgeScheduler {
-  void startScheduling();
+public class PushEventsPurgeScheduler implements Startable {
+  private static final Logger LOG = Loggers.get(PushEventsPurgeScheduler.class);
+  private static final String LOCK_NAME = "PushEventsPurgeCheck";
+  private static final String INITIAL_DELAY_IN_SECONDS = "sonar.push.events.purge.initial.delay";
+  private static final String ENQUEUE_DELAY_IN_SECONDS = "sonar.push.events.purge.enqueue.delay";
+  private static final int ENQUEUE_LOCK_DELAY_IN_SECONDS = 60;
+
+  private final DbClient dbClient;
+  private final Configuration config;
+  private final GlobalLockManager lockManager;
+  private final PushEventsPurgeExecutorService executorService;
+  private final System2 system;
+
+  public PushEventsPurgeScheduler(DbClient dbClient, Configuration config, GlobalLockManager lockManager,
+    PushEventsPurgeExecutorService executorService, System2 system) {
+    this.dbClient = dbClient;
+    this.executorService = executorService;
+    this.config = config;
+    this.lockManager = lockManager;
+    this.system = system;
+  }
+
+  @Override
+  public void start() {
+    executorService.scheduleAtFixedRate(this::checks, getInitialDelay(),
+      getEnqueueDelay(), SECONDS);
+  }
+
+  private void checks() {
+    try {
+      // Avoid enqueueing push events purge task multiple times
+      if (!lockManager.tryLock(LOCK_NAME, ENQUEUE_LOCK_DELAY_IN_SECONDS)) {
+        return;
+      }
+      purgeExpiredPushEvents();
+    } catch (Exception e) {
+      LOG.error("Error in Push Events Purge scheduler", e);
+    }
+  }
+
+  private void purgeExpiredPushEvents() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Set<String> uuids = dbClient.pushEventDao().selectUuidsOfExpiredEvents(dbSession, getExpiredTimestamp());
+      LOG.debug(String.format("%s push events to be deleted...", uuids.size()));
+      dbClient.pushEventDao().deleteByUuids(dbSession, uuids);
+      dbSession.commit();
+    }
+  }
+
+  public long getInitialDelay() {
+    return config.getLong(INITIAL_DELAY_IN_SECONDS).orElse(60 * 60L);
+  }
+
+  public long getEnqueueDelay() {
+    return config.getLong(ENQUEUE_DELAY_IN_SECONDS).orElse(60 * 60L);
+  }
+
+  private long getExpiredTimestamp() {
+    return Instant.ofEpochMilli(system.now())
+      .minus(1, ChronoUnit.HOURS)
+      .toEpochMilli();
+  }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
 }
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImpl.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImpl.java
deleted file mode 100644 (file)
index 3a72765..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.pushapi.scheduler.purge;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Set;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.server.util.GlobalLockManager;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-public class PushEventsPurgeSchedulerImpl implements PushEventsPurgeScheduler {
-  private static final Logger LOG = Loggers.get(PushEventsPurgeSchedulerImpl.class);
-  private static final String LOCK_NAME = "PushEventsPurgeCheck";
-  private static final String INITIAL_DELAY_IN_SECONDS = "sonar.push.events.purge.initial.delay";
-  private static final String ENQUEUE_DELAY_IN_SECONDS = "sonar.push.events.purge.enqueue.delay";
-  private static final int ENQUEUE_LOCK_DELAY_IN_SECONDS = 60;
-
-  private final DbClient dbClient;
-  private final Configuration config;
-  private final GlobalLockManager lockManager;
-  private final PushEventsPurgeExecutorService executorService;
-  private final System2 system;
-
-
-  public PushEventsPurgeSchedulerImpl(DbClient dbClient, Configuration config, GlobalLockManager lockManager,
-    PushEventsPurgeExecutorService executorService, System2 system) {
-    this.dbClient = dbClient;
-    this.executorService = executorService;
-    this.config = config;
-    this.lockManager = lockManager;
-    this.system = system;
-  }
-
-  @Override
-  public void startScheduling() {
-    executorService.scheduleAtFixedRate(this::checks, getInitialDelay(),
-      getEnqueueDelay(), SECONDS);
-  }
-
-  private void checks() {
-    try {
-      // Avoid enqueueing push events purge task multiple times
-      if (!lockManager.tryLock(LOCK_NAME, ENQUEUE_LOCK_DELAY_IN_SECONDS)) {
-        return;
-      }
-      purgeExpiredPushEvents();
-    } catch (Exception e) {
-      LOG.error("Error in Push Events Purge scheduler", e);
-    }
-  }
-
-  private void purgeExpiredPushEvents() {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      Set<String> uuids = dbClient.pushEventDao().selectUuidsOfExpiredEvents(dbSession, getExpiredTimestamp());
-      LOG.debug(String.format("%s push events to be deleted...", uuids.size()));
-      dbClient.pushEventDao().deleteByUuids(dbSession, uuids);
-      dbSession.commit();
-    }
-  }
-
-  public long getInitialDelay() {
-    return config.getLong(INITIAL_DELAY_IN_SECONDS).orElse(60 * 60L);
-  }
-
-  public long getEnqueueDelay() {
-    return config.getLong(ENQUEUE_DELAY_IN_SECONDS).orElse(60 * 60L);
-  }
-
-  private long getExpiredTimestamp() {
-    return Instant.ofEpochMilli(system.now())
-      .minus(1, ChronoUnit.HOURS)
-      .toEpochMilli();
-  }
-}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializerTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeInitializerTest.java
deleted file mode 100644 (file)
index 7fa2a00..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.pushapi.scheduler.purge;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class PushEventsPurgeInitializerTest {
-
-  private PushEventsPurgeScheduler pushEventsPurgeScheduler;
-
-  @Before
-  public void before() {
-    pushEventsPurgeScheduler = mock(PushEventsPurgeScheduler.class);
-  }
-
-  @Test
-  public void onServerStart_givenProjectReportScheduler_startScheduling() {
-    PushEventsPurgeInitializer pushEventsPurgeInitializer = new PushEventsPurgeInitializer(pushEventsPurgeScheduler);
-
-    pushEventsPurgeInitializer.onServerStart(null);
-
-    verify(pushEventsPurgeScheduler, times(1)).startScheduling();
-  }
-}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImplTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerImplTest.java
deleted file mode 100644 (file)
index 4e05dd2..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.pushapi.scheduler.purge;
-
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.pushevent.PushEventDao;
-import org.sonar.server.util.AbstractStoppableExecutorService;
-import org.sonar.server.util.GlobalLockManager;
-import org.sonar.server.util.GlobalLockManagerImpl;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-
-public class PushEventsPurgeSchedulerImplTest {
-  private final DbClient dbClient = mock(DbClient.class);
-  private final DbSession dbSession = mock(DbSession.class);
-  private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
-  private final PushEventDao pushEventDao = mock(PushEventDao.class);
-  private final PushEventsPurgeExecutorServiceImpl executorService = new PushEventsPurgeExecutorServiceImpl();
-  private final Configuration configuration = mock(Configuration.class);
-  private final System2 system2 = mock(System2.class);
-  private final PushEventsPurgeSchedulerImpl underTest = new PushEventsPurgeSchedulerImpl(dbClient, configuration,
-    lockManager, executorService, system2);
-
-  @Before
-  public void prepare() {
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-  }
-
-  @Test
-  public void doNothingIfLocked() {
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(false);
-
-    underTest.startScheduling();
-
-    executorService.runCommand();
-
-    verifyNoInteractions(dbClient);
-  }
-
-  @Test
-  public void doNothingIfExceptionIsThrown() {
-    when(lockManager.tryLock(any(), anyInt())).thenThrow(new IllegalArgumentException("Oops"));
-
-    underTest.startScheduling();
-
-    executorService.runCommand();
-
-    verifyNoInteractions(dbClient);
-  }
-
-  @Test
-  public void schedulePurgeTaskWhenNotLocked() {
-    when(system2.now()).thenReturn(100000000L);
-    when(dbClient.pushEventDao()).thenReturn(pushEventDao);
-    when(dbClient.openSession(false)).thenReturn(dbSession);
-    when(dbClient.pushEventDao().selectUuidsOfExpiredEvents(any(), anyLong())).thenReturn(Set.of("1", "2"));
-
-    underTest.startScheduling();
-
-    executorService.runCommand();
-
-    ArgumentCaptor<Set> uuidsCaptor = ArgumentCaptor.forClass(Set.class);
-
-    verify(pushEventDao).deleteByUuids(any(), uuidsCaptor.capture());
-    Set<String> uuids = uuidsCaptor.getValue();
-    assertThat(uuids).containsExactlyInAnyOrder("1", "2");
-  }
-
-  private static class PushEventsPurgeExecutorServiceImpl extends AbstractStoppableExecutorService<ScheduledExecutorService>
-    implements PushEventsPurgeExecutorService {
-
-    private Runnable command;
-
-    public PushEventsPurgeExecutorServiceImpl() {
-      super(null);
-    }
-
-    public void runCommand() {
-      command.run();
-    }
-
-    @Override
-    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
-      this.command = command;
-      return null;
-    }
-
-    @Override
-    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
-      return null;
-    }
-
-    @Override
-    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
-      return null;
-    }
-
-    @Override
-    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
-      return null;
-    }
-
-  }
-}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerTest.java
new file mode 100644 (file)
index 0000000..38b2fa7
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.pushapi.scheduler.purge;
+
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.pushevent.PushEventDao;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+import org.sonar.server.util.GlobalLockManager;
+import org.sonar.server.util.GlobalLockManagerImpl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+public class PushEventsPurgeSchedulerTest {
+  private final DbClient dbClient = mock(DbClient.class);
+  private final DbSession dbSession = mock(DbSession.class);
+  private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
+  private final PushEventDao pushEventDao = mock(PushEventDao.class);
+  private final PushEventsPurgeExecutorServiceImpl executorService = new PushEventsPurgeExecutorServiceImpl();
+  private final Configuration configuration = mock(Configuration.class);
+  private final System2 system2 = mock(System2.class);
+  private final PushEventsPurgeScheduler underTest = new PushEventsPurgeScheduler(dbClient, configuration,
+    lockManager, executorService, system2);
+
+  @Before
+  public void prepare() {
+    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
+  }
+
+  @Test
+  public void doNothingIfLocked() {
+    when(lockManager.tryLock(any(), anyInt())).thenReturn(false);
+
+    underTest.start();
+
+    executorService.runCommand();
+
+    verifyNoInteractions(dbClient);
+  }
+
+  @Test
+  public void doNothingIfExceptionIsThrown() {
+    when(lockManager.tryLock(any(), anyInt())).thenThrow(new IllegalArgumentException("Oops"));
+
+    underTest.start();
+
+    executorService.runCommand();
+
+    verifyNoInteractions(dbClient);
+  }
+
+  @Test
+  public void schedulePurgeTaskWhenNotLocked() {
+    when(system2.now()).thenReturn(100000000L);
+    when(dbClient.pushEventDao()).thenReturn(pushEventDao);
+    when(dbClient.openSession(false)).thenReturn(dbSession);
+    when(dbClient.pushEventDao().selectUuidsOfExpiredEvents(any(), anyLong())).thenReturn(Set.of("1", "2"));
+
+    underTest.start();
+
+    executorService.runCommand();
+
+    ArgumentCaptor<Set> uuidsCaptor = ArgumentCaptor.forClass(Set.class);
+
+    verify(pushEventDao).deleteByUuids(any(), uuidsCaptor.capture());
+    Set<String> uuids = uuidsCaptor.getValue();
+    assertThat(uuids).containsExactlyInAnyOrder("1", "2");
+  }
+
+  private static class PushEventsPurgeExecutorServiceImpl extends AbstractStoppableExecutorService<ScheduledExecutorService>
+    implements PushEventsPurgeExecutorService {
+
+    private Runnable command;
+
+    public PushEventsPurgeExecutorServiceImpl() {
+      super(null);
+    }
+
+    public void runCommand() {
+      command.run();
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+      this.command = command;
+      return null;
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+      return null;
+    }
+
+    @Override
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+      return null;
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+      return null;
+    }
+
+  }
+}