java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java
... ...
@@ -1411,17 +1411,18 @@ implements ReplicableSecurityService, ClearStateTestSupport {
1411 1411
}
1412 1412
1413 1413
/**
1414
- * Schedule a clean-up task to avoid leaking memory for the TimedLock objects; schedule it in two times the
1415
- * locking expiry of {@code timedLock}, but at least one hour, because if no authentication failure occurs
1416
- * for that IP/user agent combination, we will entirely remove the {@link TimedLock} from the map,
1417
- * effectively resetting that IP to a short default locking duration again; this way, if during the double
1418
- * expiration time another failed attempt is registered, we can still grow the locking duration because we have kept
1419
- * the {@link TimedLock} object available for a bit longer. Furthermore, for authentication requests, the
1420
- * responsible {@link Realm} will let authentication requests get to here only if not locked, so if we were to
1421
- * expunge entries immediately as they unlock, the locking duration could never grow.<p>
1414
+ * Schedule a clean-up task to avoid leaking memory for the {@link TimedLock} objects; schedule it in two times the
1415
+ * locking expiry of {@code timedLock}, but at least one hour, because if no authentication failure occurs for that
1416
+ * IP/user agent combination, we will entirely remove the {@link TimedLock} from the map, effectively resetting that
1417
+ * IP to a short default locking duration again; this way, if during the double expiration time another failed
1418
+ * attempt is registered, we can still grow the locking duration because we have kept the {@link TimedLock} object
1419
+ * available for a bit longer. Furthermore, for authentication requests, the responsible {@link Realm} will let
1420
+ * authentication requests get to here only if not locked, so if we were to expunge entries immediately as they
1421
+ * unlock, the locking duration could never grow.
1422
+ * <p>
1422 1423
*
1423
- * With the minimum of one hour, we ensure that failing requests done at a slower rate still grow the locking
1424
- * expiry duration.
1424
+ * With the minimum of one hour, we ensure that failing requests done at a slower rate still grow the locking expiry
1425
+ * duration.
1425 1426
*/
1426 1427
private void scheduleCleanUpTask(final String clientIPOrNull,
1427 1428
final TimedLock timedLock,
java/com.sap.sse.test/src/com/sap/sse/test/ThreadPoolNonDelayedTasksCountTest.java
... ...
@@ -0,0 +1,70 @@
1
+package com.sap.sse.test;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+
5
+import java.util.concurrent.ScheduledFuture;
6
+import java.util.concurrent.ScheduledThreadPoolExecutor;
7
+import java.util.concurrent.TimeUnit;
8
+
9
+import org.junit.jupiter.api.AfterEach;
10
+import org.junit.jupiter.api.BeforeEach;
11
+import org.junit.jupiter.api.Test;
12
+
13
+import com.sap.sse.common.Duration;
14
+import com.sap.sse.common.Util;
15
+import com.sap.sse.util.ThreadPoolUtil;
16
+
17
+/**
18
+ * Tests the
19
+ * {@link ThreadPoolUtil#getTasksDelayedByLessThan(java.util.concurrent.ScheduledExecutorService, com.sap.sse.common.Duration)}
20
+ * method.
21
+ *
22
+ * @author Axel Uhl (d043530)
23
+ *
24
+ */
25
+public class ThreadPoolNonDelayedTasksCountTest {
26
+ private ScheduledThreadPoolExecutor defaultBackgroundThreadPoolExecutor;
27
+ private int corePoolSize;
28
+
29
+ @BeforeEach
30
+ public void setUp() {
31
+ defaultBackgroundThreadPoolExecutor = (ScheduledThreadPoolExecutor) ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor();
32
+ corePoolSize = defaultBackgroundThreadPoolExecutor.getCorePoolSize();
33
+ }
34
+
35
+ @AfterEach
36
+ public void tearDown() {
37
+ for (final Runnable task : defaultBackgroundThreadPoolExecutor.getQueue()) {
38
+ final ScheduledFuture<?> scheduledFuture = (ScheduledFuture<?>) task;
39
+ scheduledFuture.cancel(/* mayInterruptIfRunning */ true);
40
+ }
41
+ }
42
+
43
+ @Test
44
+ public void testAddingDelayedAndUndelayedThenCounting() throws InterruptedException {
45
+ final int IMMEDIATE_TASK_COUNT = 10000;
46
+ final int DELAYED_TASK_COUNT = 100;
47
+ final Duration DELAY = Duration.ONE_HOUR.times(2);
48
+ for (int i=0; i<IMMEDIATE_TASK_COUNT; i++) {
49
+ defaultBackgroundThreadPoolExecutor.submit(()->{
50
+ try {
51
+ Thread.sleep(IMMEDIATE_TASK_COUNT);
52
+ } catch (InterruptedException e) {
53
+ e.printStackTrace();
54
+ }
55
+ });
56
+ }
57
+ for (int i=0; i<DELAYED_TASK_COUNT; i++) {
58
+ defaultBackgroundThreadPoolExecutor.schedule(()->{
59
+ try {
60
+ Thread.sleep(IMMEDIATE_TASK_COUNT);
61
+ } catch (InterruptedException e) {
62
+ e.printStackTrace();
63
+ }
64
+ }, DELAY.asMillis(), TimeUnit.MILLISECONDS);
65
+ }
66
+ Thread.sleep(100); // wait for non-delayed tasks to get scheduled
67
+ assertEquals(IMMEDIATE_TASK_COUNT-corePoolSize, Util.size(ThreadPoolUtil.INSTANCE.getTasksDelayedByLessThan(defaultBackgroundThreadPoolExecutor, DELAY.divide(2))));
68
+ assertEquals(IMMEDIATE_TASK_COUNT+DELAYED_TASK_COUNT-corePoolSize, Util.size(ThreadPoolUtil.INSTANCE.getTasksDelayedByLessThan(defaultBackgroundThreadPoolExecutor, DELAY.times(2))));
69
+ }
70
+}
java/com.sap.sse/src/com/sap/sse/util/ThreadPoolUtil.java
... ...
@@ -5,9 +5,12 @@ import java.util.concurrent.Callable;
5 5
import java.util.concurrent.ExecutorService;
6 6
import java.util.concurrent.Future;
7 7
import java.util.concurrent.ScheduledExecutorService;
8
+import java.util.concurrent.ScheduledFuture;
8 9
import java.util.concurrent.ScheduledThreadPoolExecutor;
10
+import java.util.concurrent.ThreadPoolExecutor;
9 11
import java.util.logging.Level;
10 12
13
+import com.sap.sse.common.Duration;
11 14
import com.sap.sse.util.impl.ThreadPoolUtilImpl;
12 15
13 16
public interface ThreadPoolUtil {
... ...
@@ -118,4 +121,11 @@ public interface ThreadPoolUtil {
118 121
Runnable associateWithSubjectIfAny(Runnable runnable);
119 122
120 123
<T> Callable<T> associateWithSubjectIfAny(Callable<T> callable);
124
+
125
+ /**
126
+ * In the {@code executor}'s queue filters tasks for those with a delay less than {@code delayLessThan} and
127
+ * returns the corresponding tasks. This can be used, e.g., to judge an executor's immediate workload or
128
+ * give an estimate of the future workload mapped over time.
129
+ */
130
+ Iterable<ScheduledFuture<?>> getTasksDelayedByLessThan(ThreadPoolExecutor executor, Duration delayLessThan);
121 131
}
java/com.sap.sse/src/com/sap/sse/util/impl/ThreadPoolUtilImpl.java
... ...
@@ -8,6 +8,9 @@ import java.util.concurrent.ExecutionException;
8 8
import java.util.concurrent.ExecutorService;
9 9
import java.util.concurrent.Future;
10 10
import java.util.concurrent.ScheduledExecutorService;
11
+import java.util.concurrent.ScheduledFuture;
12
+import java.util.concurrent.ThreadPoolExecutor;
13
+import java.util.concurrent.TimeUnit;
11 14
import java.util.logging.Level;
12 15
import java.util.logging.Logger;
13 16
... ...
@@ -15,6 +18,7 @@ import org.apache.shiro.SecurityUtils;
15 18
import org.apache.shiro.UnavailableSecurityManagerException;
16 19
import org.apache.shiro.subject.Subject;
17 20
21
+import com.sap.sse.common.Duration;
18 22
import com.sap.sse.common.Util;
19 23
import com.sap.sse.util.ThreadPoolUtil;
20 24
... ...
@@ -130,4 +134,11 @@ public class ThreadPoolUtilImpl implements ThreadPoolUtil {
130 134
public <T> Callable<T> associateWithSubjectIfAny(Callable<T> callable) {
131 135
return getSubjectOrNull().map(subject->subject.associateWith(callable)).orElse(callable);
132 136
}
137
+
138
+ @Override
139
+ public Iterable<ScheduledFuture<?>> getTasksDelayedByLessThan(ThreadPoolExecutor executor,
140
+ Duration delayLessThan) {
141
+ return Util.map(Util.filter(executor.getQueue(), task->((ScheduledFuture<?>) task).getDelay(TimeUnit.MILLISECONDS) < delayLessThan.asMillis()),
142
+ task->(ScheduledFuture<?>) task);
143
+ }
133 144
}