summaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java
blob: 05c3582a541157d3409f5a6f06493ea4a4554508 (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc;

import com.yahoo.jdisc.handler.RequestHandler;
import com.yahoo.jdisc.service.ClientProvider;
import com.yahoo.jdisc.service.ServerProvider;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class provides a thread-safe implementation of the {@link SharedResource} interface, and should be used for
 * all subclasses of {@link RequestHandler}, {@link ClientProvider} and {@link ServerProvider}. Once the reference count
 * of this resource reaches zero, the {@link #destroy()} method is called.
 *
 * @author Simon Thoresen Hult
 */
public abstract class AbstractResource implements SharedResource {

    private static final Logger log = Logger.getLogger(AbstractResource.class.getName());

    private final boolean debug = SharedResource.DEBUG;
    private final AtomicInteger refCount;
    private final Object monitor;
    private final Set<Throwable> activeReferences;
    private final ResourceReference initialCreationReference;

    protected AbstractResource() {
        if (!debug) {
            this.refCount = new AtomicInteger(1);
            this.monitor = null;
            this.activeReferences = null;
            this.initialCreationReference = new NoDebugResourceReference(this);
        } else {
            this.refCount = null;
            this.monitor = new Object();
            this.activeReferences = new HashSet<>();
            final Throwable referenceStack = new Throwable();
            this.activeReferences.add(referenceStack);
            this.initialCreationReference = new DebugResourceReference(this, referenceStack);
        }
    }

    @Override
    public final ResourceReference refer() {
        if (!debug) {
            addRef(1);
            return new NoDebugResourceReference(this);
        }

        final Throwable referenceStack = new Throwable();
        final String state;
        synchronized (monitor) {
            if (activeReferences.isEmpty()) {
                throw new IllegalStateException("Object is already destroyed, no more new references may be created."
                        + " State={ " + currentStateDebugWithLock() + " }");
            }
            activeReferences.add(referenceStack);
            state = currentStateDebugWithLock();
        }
        log.log(Level.FINE, referenceStack, () ->
                getClass().getName() + "@" + System.identityHashCode(this) + ".refer(): state={ " + state + " }");
        return new DebugResourceReference(this, referenceStack);
    }

    @Override
    public final void release() {
        initialCreationReference.close();
    }

    private void removeReferenceStack(final Throwable referenceStack, final Throwable releaseStack) {
        final boolean doDestroy;
        final String state;
        synchronized (monitor) {
            final boolean wasThere = activeReferences.remove(referenceStack);
            state = currentStateDebugWithLock();
            if (!wasThere) {
                throw new IllegalStateException("Reference is already released and can only be released once."
                        + " reference=" + Arrays.toString(referenceStack.getStackTrace())
                        + ". State={ " + state + "}");
            }
            doDestroy = activeReferences.isEmpty();
        }
        log.log(Level.FINE, releaseStack,
                () ->getClass().getName() + "@" + System.identityHashCode(this) + " release: state={ " + state + " }");
        if (doDestroy) {
            destroy();
        }
    }

    /**
     * <p>Returns the reference count of this resource. This typically has no value for other than single-threaded unit-
     * tests, as it is merely a snapshot of the counter.</p>
     *
     * @return The current value of the reference counter.
     */
    public final int retainCount() {
        if (!debug) {
            return refCount.get();
        }

        synchronized (monitor) {
            return activeReferences.size();
        }
    }

    /**
     * <p>This method signals that this AbstractResource can dispose of any internal resources, and commence with shut
     * down of any internal threads. This will be called once the reference count of this resource reaches zero.</p>
     */
    protected void destroy() {

    }

    private int addRef(int value) {
        while (true) {
            int prev = refCount.get();
            if (prev == 0) {
                throw new IllegalStateException(getClass().getName() + ".addRef(" + value + "):"
                        + " Object is already destroyed."
                        + " Consider toggling the " + SharedResource.SYSTEM_PROPERTY_NAME_DEBUG
                        + " system property to get debugging assistance with reference tracking.");
            }
            int next = prev + value;
            if (refCount.compareAndSet(prev, next)) {
                return next;
            }
        }
    }

    /**
     * Returns a string describing the current state of references in human-friendly terms. May be used for debugging.
     */
    public String currentState() {
        if (!debug) {
            return "Active references: " + refCount.get() + "."
                    + " Resource reference debugging is turned off. Consider toggling the "
                    + SharedResource.SYSTEM_PROPERTY_NAME_DEBUG
                    + " system property to get debugging assistance with reference tracking.";
        }
        synchronized (monitor) {
            return currentStateDebugWithLock();
        }
    }

    private String currentStateDebugWithLock() {
        return "Active references: " + makeListOfActiveReferences();
    }

    private String makeListOfActiveReferences() {
        final StringBuilder builder = new StringBuilder();
        builder.append("[");
        for (final Throwable activeReference : activeReferences) {
            builder.append(" ");
            builder.append(Arrays.toString(activeReference.getStackTrace()));
        }
        builder.append(" ]");
        return builder.toString();
    }

    private static class NoDebugResourceReference implements ResourceReference {
        private final AbstractResource resource;
        private final AtomicBoolean isReleased = new AtomicBoolean(false);

        public NoDebugResourceReference(final AbstractResource resource) {
            this.resource = resource;
        }

        @Override
        public final void close() {
            final boolean wasReleasedBefore = isReleased.getAndSet(true);
            if (wasReleasedBefore) {
                final String message = "Reference is already released and can only be released once."
                        + " State={ " + resource.currentState() + " }";
                throw new IllegalStateException(message);
            }
            int refCount = resource.addRef(-1);
            if (refCount == 0) {
                resource.destroy();
            }
        }
    }

    private static class DebugResourceReference implements ResourceReference {
        private final AbstractResource resource;
        private final Throwable referenceStack;

        public DebugResourceReference(final AbstractResource resource, final Throwable referenceStack) {
            this.resource = resource;
            this.referenceStack = referenceStack;
        }

        @Override
        public final void close() {
            final Throwable releaseStack = new Throwable();
            resource.removeReferenceStack(referenceStack, releaseStack);
        }
    }
}