Skip to content
This repository was archived by the owner on Dec 12, 2022. It is now read-only.

Commit 264746d

Browse files
authored
Merge pull request #34 from netty/tracer
Add LifecycleTracer to help debug lifecycle/ownership issues
2 parents 25a84e0 + 0867f99 commit 264746d

File tree

3 files changed

+201
-4
lines changed

3 files changed

+201
-4
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Copyright 2021 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.buffer.api;
17+
18+
import java.io.Serial;
19+
import java.util.ArrayDeque;
20+
import java.util.Set;
21+
import java.util.function.Function;
22+
import java.util.stream.Stream;
23+
24+
abstract class LifecycleTracer {
25+
public static LifecycleTracer get() {
26+
if (Trace.TRACE_LIFECYCLE_DEPTH == 0) {
27+
return new NoOpTracer();
28+
}
29+
StackTracer stackTracer = new StackTracer();
30+
stackTracer.addTrace(StackTracer.WALKER.walk(new Trace("allocate", 0)));
31+
return stackTracer;
32+
}
33+
34+
abstract void acquire(int acquires);
35+
36+
abstract void drop(int acquires);
37+
38+
abstract void close(int acquires);
39+
40+
abstract <I extends Rc<I>, T extends RcSupport<I, T>> Owned<T> send(Owned<T> send, int acquires);
41+
42+
abstract <E extends Throwable> E attachTrace(E throwable);
43+
44+
private static final class NoOpTracer extends LifecycleTracer {
45+
@Override
46+
void acquire(int acquires) {
47+
}
48+
49+
@Override
50+
void drop(int acquires) {
51+
}
52+
53+
@Override
54+
void close(int acquires) {
55+
}
56+
57+
@Override
58+
<I extends Rc<I>, T extends RcSupport<I, T>> Owned<T> send(Owned<T> send, int acquires) {
59+
return send;
60+
}
61+
62+
@Override
63+
<E extends Throwable> E attachTrace(E throwable) {
64+
return throwable;
65+
}
66+
}
67+
68+
private static final class StackTracer extends LifecycleTracer {
69+
private static final int MAX_TRACE_POINTS = Math.min(Integer.getInteger("MAX_TRACE_POINTS", 50), 1000);
70+
private static final StackWalker WALKER;
71+
static {
72+
int depth = Trace.TRACE_LIFECYCLE_DEPTH;
73+
WALKER = depth > 0 ? StackWalker.getInstance(Set.of(), depth + 2) : null;
74+
}
75+
76+
private final ArrayDeque<Trace> traces = new ArrayDeque<>();
77+
private boolean dropped;
78+
79+
@Override
80+
void acquire(int acquires) {
81+
Trace trace = WALKER.walk(new Trace("acquire", acquires));
82+
addTrace(trace);
83+
}
84+
85+
void addTrace(Trace trace) {
86+
if (traces.size() == MAX_TRACE_POINTS) {
87+
traces.pollFirst();
88+
}
89+
traces.addLast(trace);
90+
}
91+
92+
@Override
93+
void drop(int acquires) {
94+
dropped = true;
95+
addTrace(WALKER.walk(new Trace("drop", acquires)));
96+
}
97+
98+
@Override
99+
void close(int acquires) {
100+
if (!dropped) {
101+
addTrace(WALKER.walk(new Trace("close", acquires)));
102+
}
103+
}
104+
105+
@Override
106+
<I extends Rc<I>, T extends RcSupport<I, T>> Owned<T> send(Owned<T> send, int acquires) {
107+
Trace sendTrace = new Trace("send", acquires);
108+
sendTrace.sent = true;
109+
addTrace(WALKER.walk(sendTrace));
110+
return new Owned<T>() {
111+
@Override
112+
public T transferOwnership(Drop<T> drop) {
113+
sendTrace.received = WALKER.walk(new Trace("received", acquires));
114+
return send.transferOwnership(drop);
115+
}
116+
};
117+
}
118+
119+
@Override
120+
<E extends Throwable> E attachTrace(E throwable) {
121+
long timestamp = System.nanoTime();
122+
for (Trace trace : traces) {
123+
trace.attach(throwable, timestamp);
124+
}
125+
return throwable;
126+
}
127+
}
128+
129+
private static final class Trace implements Function<Stream<StackWalker.StackFrame>, Trace> {
130+
private static final int TRACE_LIFECYCLE_DEPTH;
131+
static {
132+
int traceDefault = 0;
133+
//noinspection AssertWithSideEffects
134+
assert (traceDefault = 10) > 0;
135+
TRACE_LIFECYCLE_DEPTH = Math.max(Integer.getInteger("TRACE_LIFECYCLE_DEPTH", traceDefault), 0);
136+
}
137+
138+
final String name;
139+
final int acquires;
140+
final long timestamp;
141+
boolean sent;
142+
volatile Trace received;
143+
StackWalker.StackFrame[] frames;
144+
145+
Trace(String name, int acquires) {
146+
this.name = name;
147+
this.acquires = acquires;
148+
timestamp = System.nanoTime();
149+
}
150+
151+
@Override
152+
public Trace apply(Stream<StackWalker.StackFrame> frames) {
153+
this.frames = frames.limit(TRACE_LIFECYCLE_DEPTH + 1).toArray(StackWalker.StackFrame[]::new);
154+
return this;
155+
}
156+
157+
public <E extends Throwable> void attach(E throwable, long timestamp) {
158+
Trace recv = received;
159+
String message = sent && recv == null? name + " (sent but not received)" : name;
160+
message += " (current acquires = " + acquires + ") T" + (this.timestamp - timestamp) / 1000 + "µs.";
161+
Traceback exception = new Traceback(message);
162+
StackTraceElement[] stackTrace = new StackTraceElement[frames.length];
163+
for (int i = 0; i < frames.length; i++) {
164+
stackTrace[i] = frames[i].toStackTraceElement();
165+
}
166+
exception.setStackTrace(stackTrace);
167+
if (recv != null) {
168+
recv.attach(exception, timestamp);
169+
}
170+
throwable.addSuppressed(exception);
171+
}
172+
}
173+
174+
private static final class Traceback extends Throwable {
175+
@Serial
176+
private static final long serialVersionUID = 941453986194634605L;
177+
178+
Traceback(String message) {
179+
super(message);
180+
}
181+
182+
@Override
183+
public synchronized Throwable fillInStackTrace() {
184+
return this;
185+
}
186+
}
187+
}

src/main/java/io/netty/buffer/api/RcSupport.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
public abstract class RcSupport<I extends Rc<I>, T extends RcSupport<I, T>> implements Rc<I> {
2121
private int acquires; // Closed if negative.
2222
private Drop<T> drop;
23+
private final LifecycleTracer tracer;
2324

2425
protected RcSupport(Drop<T> drop) {
2526
this.drop = drop;
27+
tracer = LifecycleTracer.get();
2628
}
2729

2830
/**
@@ -35,12 +37,13 @@ protected RcSupport(Drop<T> drop) {
3537
@Override
3638
public final I acquire() {
3739
if (acquires < 0) {
38-
throw new IllegalStateException("Resource is closed.");
40+
throw attachTrace(new IllegalStateException("Resource is closed."));
3941
}
4042
if (acquires == Integer.MAX_VALUE) {
4143
throw new IllegalStateException("Cannot acquire more references; counter would overflow.");
4244
}
4345
acquires++;
46+
tracer.acquire(acquires);
4447
return self();
4548
}
4649

@@ -54,12 +57,14 @@ public final I acquire() {
5457
@Override
5558
public final void close() {
5659
if (acquires == -1) {
57-
throw new IllegalStateException("Double-free: Resource already closed and dropped.");
60+
throw attachTrace(new IllegalStateException("Double-free: Resource already closed and dropped."));
5861
}
5962
if (acquires == 0) {
63+
tracer.drop(acquires);
6064
drop.drop(impl());
6165
}
6266
acquires--;
67+
tracer.close(acquires);
6368
}
6469

6570
/**
@@ -77,11 +82,15 @@ public final Send<I> send() {
7782
if (!isOwned()) {
7883
throw notSendableException();
7984
}
80-
var owned = prepareSend();
85+
var owned = tracer.send(prepareSend(), acquires);
8186
acquires = -2; // Close without dropping. This also ignore future double-free attempts.
8287
return new TransferSend<I, T>(owned, drop, getClass());
8388
}
8489

90+
protected <E extends Throwable> E attachTrace(E throwable) {
91+
return tracer.attachTrace(throwable);
92+
}
93+
8594
/**
8695
* Create an {@link IllegalStateException} with a custom message, tailored to this particular {@link Rc} instance,
8796
* for when the object cannot be sent for some reason.

src/main/java/io/netty/buffer/api/memseg/MemSegBuffer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,8 @@ public int bytesLeft() {
451451
@Override
452452
public void ensureWritable(int size, boolean allowCompaction) {
453453
if (!isOwned()) {
454-
throw new IllegalStateException("Buffer is not owned. Only owned buffers can call ensureWritable.");
454+
throw attachTrace(new IllegalStateException(
455+
"Buffer is not owned. Only owned buffers can call ensureWritable."));
455456
}
456457
if (size < 0) {
457458
throw new IllegalArgumentException("Cannot ensure writable for a negative size: " + size + '.');

0 commit comments

Comments
 (0)