Skip to content

Commit 0699bc5

Browse files
Use static exceptions for closing websocket flushers and in ContentProducer (#8155)
* Use StaticException class in jetty-util for websocket flushers. * Use StaticException class for ContentProducer recycle and consumeAll Signed-off-by: Lachlan Roberts <[email protected]> Signed-off-by: Ludovic Orban <[email protected]> Co-authored-by: Ludovic Orban <[email protected]>
1 parent b1c19c0 commit 0699bc5

File tree

6 files changed

+85
-34
lines changed

6 files changed

+85
-34
lines changed

jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.eclipse.jetty.http.BadMessageException;
2121
import org.eclipse.jetty.http.HttpStatus;
22+
import org.eclipse.jetty.util.StaticException;
2223
import org.eclipse.jetty.util.component.Destroyable;
2324
import org.eclipse.jetty.util.thread.AutoLock;
2425
import org.slf4j.Logger;
@@ -31,15 +32,8 @@
3132
class AsyncContentProducer implements ContentProducer
3233
{
3334
private static final Logger LOG = LoggerFactory.getLogger(AsyncContentProducer.class);
34-
private static final HttpInput.ErrorContent RECYCLED_ERROR_CONTENT = new HttpInput.ErrorContent(new IllegalStateException("ContentProducer has been recycled"));
35-
private static final Throwable UNCONSUMED_CONTENT_EXCEPTION = new IOException("Unconsumed content")
36-
{
37-
@Override
38-
public Throwable fillInStackTrace()
39-
{
40-
return this;
41-
}
42-
};
35+
private static final HttpInput.ErrorContent RECYCLED_ERROR_CONTENT = new HttpInput.ErrorContent(new StaticException("ContentProducer has been recycled"));
36+
private static final Throwable UNCONSUMED_CONTENT_EXCEPTION = new StaticException("Unconsumed content");
4337

4438
private final AutoLock _lock = new AutoLock();
4539
private final HttpChannel _httpChannel;
@@ -188,10 +182,10 @@ public boolean consumeAll()
188182
{
189183
assertLocked();
190184
Throwable x = UNCONSUMED_CONTENT_EXCEPTION;
191-
if (LOG.isDebugEnabled())
185+
if (LOG.isTraceEnabled())
192186
{
193-
x = new IOException("Unconsumed content");
194-
LOG.debug("consumeAll {}", this, x);
187+
x = new StaticException("Unconsumed content", true);
188+
LOG.trace("consumeAll {}", this, x);
195189
}
196190
failCurrentContent(x);
197191
// A specific HttpChannel mechanism must be used as the following code
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// ========================================================================
3+
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
4+
//
5+
// This program and the accompanying materials are made available under the
6+
// terms of the Eclipse Public License v. 2.0 which is available at
7+
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
8+
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
9+
//
10+
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
11+
// ========================================================================
12+
//
13+
14+
package org.eclipse.jetty.util;
15+
16+
/**
17+
* This exception can safely be stored in a static variable as suppressed exceptions are disabled,
18+
* meaning calling {@link #addSuppressed(Throwable)} has no effect.
19+
* This prevents potential memory leaks where a statically-stored exception would accumulate
20+
* suppressed exceptions added to them.
21+
*/
22+
public class StaticException extends Exception
23+
{
24+
/**
25+
* Create an instance with writable stack trace and suppression disabled.
26+
*
27+
* @param message – the detail message
28+
*
29+
* @see Throwable#Throwable(String, Throwable, boolean, boolean)
30+
*/
31+
public StaticException(String message)
32+
{
33+
this(message, false);
34+
}
35+
36+
/**
37+
* Create an instance with suppression disabled.
38+
*
39+
* @param message – the detail message
40+
* @param writableStackTrace whether or not the stack trace should be writable
41+
*
42+
* @see Throwable#Throwable(String, Throwable, boolean, boolean)
43+
*/
44+
public StaticException(String message, boolean writableStackTrace)
45+
{
46+
// Make sure to call the super constructor that disables suppressed exception.
47+
super(message, null, false, writableStackTrace);
48+
}
49+
}

jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/DemandingFlusher.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.eclipse.jetty.util.Callback;
2121
import org.eclipse.jetty.util.CountingCallback;
2222
import org.eclipse.jetty.util.IteratingCallback;
23+
import org.eclipse.jetty.util.StaticException;
2324
import org.eclipse.jetty.websocket.core.Extension;
2425
import org.eclipse.jetty.websocket.core.Frame;
2526
import org.eclipse.jetty.websocket.core.IncomingFrames;
@@ -38,6 +39,8 @@
3839
*/
3940
public abstract class DemandingFlusher extends IteratingCallback implements DemandChain
4041
{
42+
private static final Throwable SENTINEL_CLOSE_EXCEPTION = new StaticException("Closed");
43+
4144
private final IncomingFrames _emitFrame;
4245
private final AtomicLong _demand = new AtomicLong();
4346
private final AtomicReference<Throwable> _failure = new AtomicReference<>();
@@ -101,6 +104,18 @@ public void onFrame(Frame frame, Callback callback)
101104
succeeded();
102105
}
103106

107+
/**
108+
* Used to close this flusher when there is no explicit failure.
109+
*/
110+
public void closeFlusher()
111+
{
112+
if (_failure.compareAndSet(null, SENTINEL_CLOSE_EXCEPTION))
113+
{
114+
failed(SENTINEL_CLOSE_EXCEPTION);
115+
iterate();
116+
}
117+
}
118+
104119
/**
105120
* Used to fail this flusher possibly from an external event such as a callback.
106121
* @param t the failure.

jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.eclipse.jetty.util.BufferUtil;
3030
import org.eclipse.jetty.util.Callback;
3131
import org.eclipse.jetty.util.IteratingCallback;
32+
import org.eclipse.jetty.util.StaticException;
3233
import org.eclipse.jetty.util.TypeUtil;
3334
import org.eclipse.jetty.util.thread.AutoLock;
3435
import org.eclipse.jetty.util.thread.Scheduler;
@@ -44,6 +45,7 @@ public class FrameFlusher extends IteratingCallback
4445
{
4546
public static final Frame FLUSH_FRAME = new Frame(OpCode.BINARY);
4647
private static final Logger LOG = LoggerFactory.getLogger(FrameFlusher.class);
48+
private static final Throwable CLOSED_CHANNEL = new StaticException("Closed");
4749

4850
private final AutoLock lock = new AutoLock();
4951
private final LongAdder messagesOut = new LongAdder();
@@ -184,15 +186,7 @@ public void onClose(Throwable cause)
184186
{
185187
try (AutoLock l = lock.lock())
186188
{
187-
// TODO: find a way to not create exception if cause is null.
188-
closedCause = cause == null ? new ClosedChannelException()
189-
{
190-
@Override
191-
public Throwable fillInStackTrace()
192-
{
193-
return this;
194-
}
195-
} : cause;
189+
closedCause = cause == null ? CLOSED_CHANNEL : cause;
196190
}
197191
iterate();
198192
}

jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/PerMessageDeflateExtension.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
package org.eclipse.jetty.websocket.core.internal;
1515

1616
import java.nio.ByteBuffer;
17-
import java.nio.channels.ClosedChannelException;
1817
import java.util.HashMap;
1918
import java.util.Map;
2019
import java.util.concurrent.atomic.AtomicReference;
@@ -150,17 +149,8 @@ public void init(final ExtensionConfig config, WebSocketComponents components)
150149
@Override
151150
public void close()
152151
{
153-
// TODO: use IteratingCallback.close() instead of creating exception with failFlusher methods.
154-
ClosedChannelException exception = new ClosedChannelException()
155-
{
156-
@Override
157-
public Throwable fillInStackTrace()
158-
{
159-
return this;
160-
}
161-
};
162-
incomingFlusher.failFlusher(exception);
163-
outgoingFlusher.failFlusher(exception);
152+
incomingFlusher.closeFlusher();
153+
outgoingFlusher.closeFlusher();
164154
releaseInflater();
165155
releaseDeflater();
166156
}

jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/TransformingFlusher.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.eclipse.jetty.util.Callback;
2020
import org.eclipse.jetty.util.IteratingCallback;
21+
import org.eclipse.jetty.util.StaticException;
2122
import org.eclipse.jetty.util.thread.AutoLock;
2223
import org.eclipse.jetty.websocket.core.Frame;
2324
import org.slf4j.Logger;
@@ -33,6 +34,7 @@
3334
public abstract class TransformingFlusher
3435
{
3536
private final Logger log = LoggerFactory.getLogger(this.getClass());
37+
private static final Throwable SENTINEL_CLOSE_EXCEPTION = new StaticException("Closed");
3638

3739
private final AutoLock lock = new AutoLock();
3840
private final Queue<FrameEntry> entries = new ArrayDeque<>();
@@ -77,13 +79,20 @@ public final void sendFrame(Frame frame, Callback callback, boolean batch)
7779
notifyCallbackFailure(callback, failure);
7880
}
7981

82+
/**
83+
* Used to close this flusher when there is no explicit failure.
84+
*/
85+
public void closeFlusher()
86+
{
87+
failFlusher(SENTINEL_CLOSE_EXCEPTION);
88+
}
89+
8090
/**
8191
* Used to fail this flusher possibly from an external event such as a callback.
8292
* @param t the failure.
8393
*/
8494
public void failFlusher(Throwable t)
8595
{
86-
// TODO: find a way to close the flusher in non error case without exception.
8796
boolean failed = false;
8897
try (AutoLock l = lock.lock())
8998
{

0 commit comments

Comments
 (0)