Skip to content

Commit 4d97dea

Browse files
committed
Updates to the AuthenticationRefresher timer
The AuthenticationRefreher will now keep a instance-level reference to the current Executor. A singleton style synchronization ensures there is only ever one Executor active. Additionally, the APIClient passed into .provide is now heald as a WeakReference by the Executor. if the APIClient is ever garbage collected the Executor is cancelled and nulled.
1 parent b71fba4 commit 4d97dea

File tree

3 files changed

+108
-25
lines changed

3 files changed

+108
-25
lines changed

util/src/main/java/io/kubernetes/client/util/credentials/AuthenticationRefresher.java

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
*/
1313
package io.kubernetes.client.util.credentials;
1414

15+
import java.lang.ref.WeakReference;
1516
import java.util.concurrent.Executors;
1617
import java.util.concurrent.ScheduledExecutorService;
18+
import java.util.concurrent.ScheduledFuture;
1719

1820
import org.slf4j.Logger;
1921
import org.slf4j.LoggerFactory;
@@ -26,11 +28,12 @@
2628
*
2729
* Can be used with ClientBuilder as such:
2830
*
29-
* <pre>
30-
* ClientBuilder.standard()
31-
* .withAuthentication(new AuthenticationRefresher(new KubeconfigAuthentication(), 60))
32-
* .build();
33-
* </pre>
31+
* <pre> ClientBuilder.standard() .withAuthentication(new
32+
* AuthenticationRefresher(new KubeconfigAuthentication(), 60)) .build(); </pre>
33+
*
34+
* The AuthenticationRefresher maintains a {@link WeakReference} to the
35+
* ApiClient passed into provide. When the client instance goes out of scope,
36+
* the refresh timer will be cancelled and released.
3437
*/
3538
public class AuthenticationRefresher implements Authentication {
3639

@@ -39,9 +42,13 @@ public class AuthenticationRefresher implements Authentication {
3942
private final Authentication delegateAuthentication;
4043
private final Long expirationSeconds;
4144

45+
private final ScheduledExecutorService executor;
46+
private ScheduledFuture<?> currentSchedule;
47+
4248
public AuthenticationRefresher(Authentication delegateAuthentication, long expirationSeconds) {
4349
this.delegateAuthentication = delegateAuthentication;
4450
this.expirationSeconds = expirationSeconds;
51+
this.executor = Executors.newSingleThreadScheduledExecutor();
4552
log.debug("AuthenticationRefresher initialized with expirationSeconds: " + expirationSeconds);
4653
}
4754

@@ -50,17 +57,40 @@ public AuthenticationRefresher(Authentication delegateAuthentication, long expir
5057
*/
5158
@Override
5259
public void provide(ApiClient client) {
53-
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
54-
executor.scheduleAtFixedRate(new Runnable() {
60+
61+
synchronized (this) {
62+
if (currentSchedule == null) {
63+
currentSchedule = scheduleRefresh(client);
64+
}
65+
}
66+
67+
// Run it now, synchronously.
68+
log.debug("Invoking authentication");
69+
this.delegateAuthentication.provide(client);
70+
}
71+
72+
private ScheduledFuture<?> scheduleRefresh(final ApiClient clientArg) {
73+
WeakReference<ApiClient> clientReference = new WeakReference<>(clientArg);
74+
75+
return executor.scheduleAtFixedRate(new Runnable() {
5576
@Override
5677
public void run() {
5778
log.debug("Refreshing authentication");
58-
delegateAuthentication.provide(client);
79+
80+
ApiClient client = clientReference.get();
81+
82+
if (client == null) {
83+
log.debug("Delegate authentication is null, stopping");
84+
currentSchedule.cancel(true);
85+
currentSchedule = null;
86+
return;
87+
} else {
88+
log.debug("Invoking authentication");
89+
delegateAuthentication.provide(client);
90+
}
91+
5992
}
6093
}, expirationSeconds, expirationSeconds, java.util.concurrent.TimeUnit.SECONDS);
6194

62-
// Run it now, synchronously.
63-
log.debug("Invoking authentication");
64-
delegateAuthentication.provide(client);
6595
}
6696
}

util/src/test/java/io/kubernetes/client/util/ClientBuilderTest.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
import static io.kubernetes.client.util.Config.ENV_SERVICE_PORT;
1717
import static org.assertj.core.api.Assertions.assertThat;
1818
import static org.assertj.core.api.Assertions.assertThatThrownBy;
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
1920
import static org.mockito.Mockito.mock;
20-
import static org.mockito.Mockito.times;
2121
import static org.mockito.Mockito.verify;
2222

2323
import java.io.File;
@@ -26,6 +26,8 @@
2626
import java.nio.file.Files;
2727
import java.nio.file.Paths;
2828
import java.time.Duration;
29+
import java.util.concurrent.CountDownLatch;
30+
import java.util.concurrent.TimeUnit;
2931

3032
import org.junit.jupiter.api.Test;
3133
import org.junit.jupiter.api.extension.ExtendWith;
@@ -224,15 +226,20 @@ void credentialProviderInvoked() throws IOException {
224226

225227
@Test
226228
void credentialProviderWrappedWithRefresher() throws IOException, InterruptedException {
227-
final Authentication provider = mock(Authentication.class);
228-
final ApiClient client = ClientBuilder.standard()
229+
CountDownLatch latch = new CountDownLatch(2);
230+
final Authentication provider = new Authentication() {
231+
@Override
232+
public void provide(ApiClient client) {
233+
latch.countDown();
234+
}
235+
};
236+
ClientBuilder.standard()
229237
.setAuthentication(provider)
230238
.setAuthenticationRefreshSeconds(Duration.ofSeconds(2))
231239
.build();
232240

233-
Thread.sleep(3000);
234-
235-
verify(provider, times(2)).provide(client);
241+
boolean result = latch.await(4, TimeUnit.SECONDS);
242+
assertTrue(result, "CountDownLatch timed out, which means our provider was not called twice.");
236243
}
237244

238245
/**

util/src/test/java/io/kubernetes/client/util/credentials/AuthenticationRefresherTest.java

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
*/
1313
package io.kubernetes.client.util.credentials;
1414

15+
import static org.junit.jupiter.api.Assertions.assertFalse;
16+
import static org.junit.jupiter.api.Assertions.assertTrue;
1517
import static org.mockito.Mockito.mock;
16-
import static org.mockito.Mockito.times;
1718
import static org.mockito.Mockito.verify;
1819

1920
import java.io.IOException;
21+
import java.lang.ref.WeakReference;
22+
import java.util.concurrent.CountDownLatch;
23+
import java.util.concurrent.TimeUnit;
2024

2125
import org.junit.jupiter.api.Test;
2226

@@ -32,21 +36,63 @@ void credentialProviderWrapperInvoked() throws IOException {
3236

3337
final ApiClient client = ClientBuilder.standard().setAuthentication(wrapper).build();
3438

39+
//No latch here, just need to make sure it got called once.
3540
verify(provider).provide(client);
3641
}
3742

38-
@Test
39-
void credentialProviderTimerInvoked() throws Exception {
40-
final Authentication provider = mock(Authentication.class);
43+
/**
44+
* CountdownLatch should be called 2 times in this teest. Once
45+
* synchronously, and once asynchronously.
46+
*
47+
* @throws Exception
48+
*/
49+
@Test void credentialProviderTimerInvoked() throws Exception {
50+
CountDownLatch latch = new CountDownLatch(2);
51+
52+
final Authentication provider = new Authentication() {
53+
@Override
54+
public void provide(ApiClient client) {
55+
latch.countDown();
56+
}
57+
};
4158
final Authentication wrapper = new AuthenticationRefresher(provider, 2);
4259

43-
final ApiClient client = ClientBuilder.standard().setAuthentication(wrapper).build();
60+
ClientBuilder.standard().setAuthentication(wrapper).build();
61+
62+
boolean result = latch.await(4, TimeUnit.SECONDS);
63+
assertTrue(result, "CountDownLatch timed out, which means our provider was not called twice.");
64+
}
65+
66+
/**
67+
* CountdownLatch should be called 1 time in this teest. Once
68+
* synchronously, and then the timer should cancel.
69+
*
70+
* @throws Exception
71+
*/
72+
@Test void credentialProviderTimerInvokedOnce() throws Exception {
73+
CountDownLatch latch = new CountDownLatch(2);
74+
75+
final Authentication provider = new Authentication() {
76+
@Override
77+
public void provide(ApiClient client) {
78+
latch.countDown();
79+
}
80+
};
81+
final Authentication wrapper = new AuthenticationRefresher(provider, 2);
4482

45-
Thread.sleep(3000);
83+
ClientBuilder.standard().setAuthentication(wrapper).build();
4684

47-
// verify provider mock called 2 times
48-
verify(provider, times(2)).provide(client);
85+
//Crazy hack to force garbage collection and wait for it
86+
Object obj = new Object();
87+
WeakReference<Object> ref = new WeakReference<>(obj);
88+
obj = null;
89+
while (ref.get() != null) {
90+
System.gc();
91+
}
4992

93+
boolean result = latch.await(4, TimeUnit.SECONDS);
94+
assertFalse(result,
95+
"CountDownLatch SHOULD HAVE timed out, which means the ScheduledExecutorService was not cancelled on GC.");
5096
}
5197

5298
}

0 commit comments

Comments
 (0)