aboutsummaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/main/java/com/yahoo/jdisc/refcount/DebugReferencesWithStack.java
blob: 0dd3df3cf13190e837887927bb2585a9130cf457 (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
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.refcount;

import com.yahoo.jdisc.ResourceReference;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Does reference counting by putting stacktraces in a map together with an optional context.
 * Intended only for debugging as it is slow.
 * Used if system property jdisc.debug.resources=stack
 *
 * @author baldersheim
 */
public class DebugReferencesWithStack implements References {

    private static final Logger log = Logger.getLogger(DebugReferencesWithStack.class.getName());
    private final Map<Throwable, Object> activeReferences = new HashMap<>();
    private final DestructableResource resource;
    private final DebugResourceReference initialreference;

    public DebugReferencesWithStack(DestructableResource resource) {
        final Throwable referenceStack = new Throwable();
        this.activeReferences.put(referenceStack, this);
        this.resource = resource;
        initialreference = new DebugResourceReference(this, referenceStack);
    }

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

    @Override
    public int referenceCount() {
        synchronized (activeReferences) {
            return activeReferences.size();
        }
    }

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

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

        if (doDestroy) {
            resource.close();
        }
    }

    @Override
    public String currentState() {
        return "Active references: " + makeListOfActiveReferences();
    }

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

    private static class DebugResourceReference extends CloseableOnce {
        private final DebugReferencesWithStack resource;
        private final Throwable referenceStack;

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

        @Override
        final void onClose() {
            final Throwable releaseStack = new Throwable();
            resource.removeReferenceStack(referenceStack, releaseStack);
        }
        @Override
        final References getReferences() { return resource; }
    }
}