summaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerDeactivationWatchdogTest.java
blob: 05207728c88ad6dd6c9f99a30a872a7167bf4092 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.core;

import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.ResourceReference;
import com.yahoo.jdisc.statistics.ActiveContainerMetrics;
import com.yahoo.jdisc.test.TestDriver;
import com.yahoo.test.ManualClock;
import org.junit.Ignore;
import org.junit.Test;

import java.lang.ref.WeakReference;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

/**
 * @author bjorncs
 */
public class ActiveContainerDeactivationWatchdogTest {

    @Test
    public void watchdog_counts_deactivated_containers() {
        TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
        ManualClock clock = new ManualClock(Instant.now());
        ActiveContainerDeactivationWatchdog watchdog =
                new ActiveContainerDeactivationWatchdog(clock, Executors.newScheduledThreadPool(1));
        MockMetric metric = new MockMetric();

        ActiveContainer containerWithoutRetainedResources = new ActiveContainer(driver.newContainerBuilder());

        watchdog.onContainerActivation(containerWithoutRetainedResources);
        watchdog.emitMetrics(metric);
        assertEquals(0, metric.totalCount);
        assertEquals(0, metric.withRetainedReferencesCount);

        watchdog.onContainerActivation(null);
        containerWithoutRetainedResources.release();
        clock.advance(ActiveContainerDeactivationWatchdog.ACTIVE_CONTAINER_GRACE_PERIOD);
        watchdog.emitMetrics(metric);
        assertEquals(0, metric.totalCount);
        assertEquals(0, metric.withRetainedReferencesCount);

        clock.advance(Duration.ofSeconds(1));
        watchdog.emitMetrics(metric);
        assertEquals(1, metric.totalCount);
        assertEquals(0, metric.withRetainedReferencesCount);

        ActiveContainer containerWithRetainedResources = new ActiveContainer(driver.newContainerBuilder());
        try (ResourceReference ignoredRef = containerWithRetainedResources.refer()) {
            watchdog.onContainerActivation(containerWithRetainedResources);
            containerWithRetainedResources.release();
            watchdog.onContainerActivation(null);
            clock.advance(ActiveContainerDeactivationWatchdog.ACTIVE_CONTAINER_GRACE_PERIOD.plusSeconds(1));
            watchdog.emitMetrics(metric);
            assertEquals(2, metric.totalCount);
            assertEquals(1, metric.withRetainedReferencesCount);
        }

    }

    @Test
    @Ignore("JVM does not give any guarantee when phantom references will be enqueued to reference queues")
    public void deactivated_container_destructed_if_its_reference_counter_is_nonzero() {
        ExecutorMock executor = new ExecutorMock();
        ActiveContainerDeactivationWatchdog watchdog =
                new ActiveContainerDeactivationWatchdog(new ManualClock(), executor);
        ActiveContainer container =
                new ActiveContainer(TestDriver.newSimpleApplicationInstanceWithoutOsgi().newContainerBuilder());
        AtomicBoolean destructed = new AtomicBoolean(false);
        container.shutdown().notifyTermination(() -> destructed.set(true));

        container.refer(); // increase reference counter to simluate a leaking resource
        watchdog.onContainerActivation(container);
        container.release(); // release resource
        watchdog.onContainerActivation(null); // deactive container

        WeakReference<ActiveContainer> containerWeakReference = new WeakReference<>(container);
        container = null; // make container instance collectable by GC
        System.gc();

        assertNull("Container is not GCed - probably because the watchdog has a concrete reference to it",
                   containerWeakReference.get());
        executor.containerDestructorCommand.run();
        assertTrue("Destructor is not called on deactivated container", destructed.get());
    }

    private static class MockMetric implements Metric {
        public int totalCount;
        public int withRetainedReferencesCount;

        @Override
        public void set(String key, Number val, Context ctx) {
            switch (key) {
                case ActiveContainerMetrics.TOTAL_DEACTIVATED_CONTAINERS:
                    totalCount = val.intValue();
                    break;
                case ActiveContainerMetrics.DEACTIVATED_CONTAINERS_WITH_RETAINED_REFERENCES:
                    withRetainedReferencesCount = val.intValue();
                    break;
                default:
                    throw new UnsupportedOperationException();
            }
        }

        @Override
        public void add(String key, Number val, Context ctx) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Context createContext(Map<String, ?> properties) {
            throw new UnsupportedOperationException();
        }
    }

    private static class ExecutorMock extends ScheduledThreadPoolExecutor {

        public Runnable containerDestructorCommand;
        private int registrationCounter = 0;

        public ExecutorMock() {
            super(1);
        }

        @Override
        public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
            if (registrationCounter == 2) {
                containerDestructorCommand = command;
            } else if (registrationCounter > 2){
                throw new IllegalStateException("Unexpected registration");
            }
            ++registrationCounter;
            return null;
        }
    }

}