Skip to content

Commit 15b106c

Browse files
authored
Allocator support exitOnOutOfMemory config. (#3984)
* Allocator support exitOnOutOfMemory config.
1 parent 4d50a44 commit 15b106c

File tree

12 files changed

+201
-7
lines changed

12 files changed

+201
-7
lines changed

bookkeeper-common-allocator/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
<groupId>io.netty</groupId>
3030
<artifactId>netty-buffer</artifactId>
3131
</dependency>
32+
<dependency>
33+
<groupId>org.mockito</groupId>
34+
<artifactId>mockito-core</artifactId>
35+
<scope>test</scope>
36+
</dependency>
3237
</dependencies>
3338
<build>
3439
<plugins>

bookkeeper-common-allocator/src/main/java/org/apache/bookkeeper/common/allocator/ByteBufAllocatorBuilder.java

+2
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,6 @@ static ByteBufAllocatorBuilder create() {
9292
* <p>Default is {@link LeakDetectionPolicy#Disabled}
9393
*/
9494
ByteBufAllocatorBuilder leakDetectionPolicy(LeakDetectionPolicy leakDetectionPolicy);
95+
96+
ByteBufAllocatorBuilder exitOnOutOfMemory(boolean exitOnOutOfMemory);
9597
}

bookkeeper-common-allocator/src/main/java/org/apache/bookkeeper/common/allocator/impl/ByteBufAllocatorBuilderImpl.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ public class ByteBufAllocatorBuilderImpl implements ByteBufAllocatorBuilder {
3737
OutOfMemoryPolicy outOfMemoryPolicy = OutOfMemoryPolicy.FallbackToHeap;
3838
Consumer<OutOfMemoryError> outOfMemoryListener = null;
3939
LeakDetectionPolicy leakDetectionPolicy = LeakDetectionPolicy.Disabled;
40+
boolean exitOnOutOfMemory = false;
4041

4142
@Override
4243
public ByteBufAllocatorWithOomHandler build() {
4344
return new ByteBufAllocatorImpl(pooledAllocator, unpooledAllocator, poolingPolicy, poolingConcurrency,
44-
outOfMemoryPolicy, outOfMemoryListener, leakDetectionPolicy);
45+
outOfMemoryPolicy, outOfMemoryListener, leakDetectionPolicy, exitOnOutOfMemory);
4546
}
4647

4748
@Override
@@ -86,4 +87,10 @@ public ByteBufAllocatorBuilder leakDetectionPolicy(LeakDetectionPolicy leakDetec
8687
return this;
8788
}
8889

90+
@Override
91+
public ByteBufAllocatorBuilder exitOnOutOfMemory(boolean exitOnOutOfMemory) {
92+
this.exitOnOutOfMemory = exitOnOutOfMemory;
93+
return this;
94+
}
95+
8996
}

bookkeeper-common-allocator/src/main/java/org/apache/bookkeeper/common/allocator/impl/ByteBufAllocatorImpl.java

+28-6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.bookkeeper.common.allocator.LeakDetectionPolicy;
3030
import org.apache.bookkeeper.common.allocator.OutOfMemoryPolicy;
3131
import org.apache.bookkeeper.common.allocator.PoolingPolicy;
32+
import org.apache.bookkeeper.common.util.ShutdownUtil;
3233
import org.slf4j.Logger;
3334
import org.slf4j.LoggerFactory;
3435

@@ -48,15 +49,24 @@ public class ByteBufAllocatorImpl extends AbstractByteBufAllocator implements By
4849
private final PoolingPolicy poolingPolicy;
4950
private final OutOfMemoryPolicy outOfMemoryPolicy;
5051
private Consumer<OutOfMemoryError> outOfMemoryListener;
52+
private final boolean exitOnOutOfMemory;
5153

5254
ByteBufAllocatorImpl(ByteBufAllocator pooledAllocator, ByteBufAllocator unpooledAllocator,
5355
PoolingPolicy poolingPolicy, int poolingConcurrency, OutOfMemoryPolicy outOfMemoryPolicy,
5456
Consumer<OutOfMemoryError> outOfMemoryListener,
5557
LeakDetectionPolicy leakDetectionPolicy) {
56-
super(poolingPolicy == PoolingPolicy.PooledDirect /* preferDirect */);
58+
this(pooledAllocator, unpooledAllocator, poolingPolicy, poolingConcurrency, outOfMemoryPolicy,
59+
outOfMemoryListener, leakDetectionPolicy, false);
60+
}
5761

62+
ByteBufAllocatorImpl(ByteBufAllocator pooledAllocator, ByteBufAllocator unpooledAllocator,
63+
PoolingPolicy poolingPolicy, int poolingConcurrency, OutOfMemoryPolicy outOfMemoryPolicy,
64+
Consumer<OutOfMemoryError> outOfMemoryListener,
65+
LeakDetectionPolicy leakDetectionPolicy, boolean exitOnOutOfMemory) {
66+
super(poolingPolicy == PoolingPolicy.PooledDirect /* preferDirect */);
5867
this.poolingPolicy = poolingPolicy;
5968
this.outOfMemoryPolicy = outOfMemoryPolicy;
69+
this.exitOnOutOfMemory = exitOnOutOfMemory;
6070
if (outOfMemoryListener == null) {
6171
this.outOfMemoryListener = (v) -> {
6272
log.error("Unable to allocate memory", v);
@@ -146,7 +156,7 @@ protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
146156
: unpooledAllocator;
147157
return alloc.heapBuffer(initialCapacity, maxCapacity);
148158
} catch (OutOfMemoryError e) {
149-
outOfMemoryListener.accept(e);
159+
consumeOOMError(e);
150160
throw e;
151161
}
152162
}
@@ -166,12 +176,12 @@ private ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity, boolean ca
166176
try {
167177
return unpooledAllocator.heapBuffer(initialCapacity, maxCapacity);
168178
} catch (OutOfMemoryError e2) {
169-
outOfMemoryListener.accept(e2);
179+
consumeOOMError(e2);
170180
throw e2;
171181
}
172182
} else {
173183
// ThrowException
174-
outOfMemoryListener.accept(e);
184+
consumeOOMError(e);
175185
throw e;
176186
}
177187
}
@@ -181,12 +191,24 @@ private ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity, boolean ca
181191
try {
182192
return unpooledAllocator.directBuffer(initialCapacity, maxCapacity);
183193
} catch (OutOfMemoryError e) {
184-
outOfMemoryListener.accept(e);
185-
throw e;
194+
consumeOOMError(e);
195+
throw e;
186196
}
187197
}
188198
}
189199

200+
private void consumeOOMError(OutOfMemoryError outOfMemoryError) {
201+
try {
202+
outOfMemoryListener.accept(outOfMemoryError);
203+
} catch (Throwable e) {
204+
log.warn("Consume outOfMemory error failed.", e);
205+
}
206+
if (exitOnOutOfMemory) {
207+
log.info("Exiting JVM process for OOM error: {}", outOfMemoryError.getMessage(), outOfMemoryError);
208+
ShutdownUtil.triggerImmediateForcefulShutdown();
209+
}
210+
}
211+
190212
@Override
191213
public boolean isDirectBufferPooled() {
192214
return pooledAllocator != null && pooledAllocator.isDirectBufferPooled();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.bookkeeper.common.util;
20+
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.lang.reflect.Method;
23+
import lombok.extern.slf4j.Slf4j;
24+
25+
/**
26+
* Forked from <a href="https://github.com/apache/pulsar">Pulsar</a>.
27+
*/
28+
@Slf4j
29+
public class ShutdownUtil {
30+
private static final Method log4j2ShutdownMethod;
31+
32+
static {
33+
// use reflection to find org.apache.logging.log4j.LogManager.shutdown method
34+
Method shutdownMethod = null;
35+
try {
36+
shutdownMethod = Class.forName("org.apache.logging.log4j.LogManager")
37+
.getMethod("shutdown");
38+
} catch (ClassNotFoundException | NoSuchMethodException e) {
39+
// ignore when Log4j2 isn't found, log at debug level
40+
log.debug("Cannot find org.apache.logging.log4j.LogManager.shutdown method", e);
41+
}
42+
log4j2ShutdownMethod = shutdownMethod;
43+
}
44+
45+
/**
46+
* Triggers an immediate forceful shutdown of the current process.
47+
*
48+
* @param status Termination status. By convention, a nonzero status code indicates abnormal termination.
49+
* @see Runtime#halt(int)
50+
*/
51+
public static void triggerImmediateForcefulShutdown(int status) {
52+
triggerImmediateForcefulShutdown(status, true);
53+
}
54+
public static void triggerImmediateForcefulShutdown(int status, boolean logging) {
55+
try {
56+
if (status != 0 && logging) {
57+
log.warn("Triggering immediate shutdown of current process with status {}", status,
58+
new Exception("Stacktrace for immediate shutdown"));
59+
}
60+
shutdownLogging();
61+
} finally {
62+
Runtime.getRuntime().halt(status);
63+
}
64+
}
65+
66+
private static void shutdownLogging() {
67+
// flush log buffers and shutdown log4j2 logging to prevent log truncation
68+
if (log4j2ShutdownMethod != null) {
69+
try {
70+
// use reflection to call org.apache.logging.log4j.LogManager.shutdown()
71+
log4j2ShutdownMethod.invoke(null);
72+
} catch (IllegalAccessException | InvocationTargetException e) {
73+
log.error("Unable to call org.apache.logging.log4j.LogManager.shutdown using reflection.", e);
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Triggers an immediate forceful shutdown of the current process using 1 as the status code.
80+
*
81+
* @see Runtime#halt(int)
82+
*/
83+
public static void triggerImmediateForcefulShutdown() {
84+
triggerImmediateForcefulShutdown(1);
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
/**
19+
* defines the utilities for allocator used across the project.
20+
*/
21+
package org.apache.bookkeeper.common.util;

bookkeeper-common-allocator/src/test/java/org/apache/bookkeeper/common/allocator/impl/ByteBufAllocatorBuilderTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static org.junit.Assert.fail;
2424
import static org.mockito.ArgumentMatchers.anyInt;
2525
import static org.mockito.Mockito.mock;
26+
import static org.mockito.Mockito.mockStatic;
2627
import static org.mockito.Mockito.when;
2728

2829
import io.netty.buffer.ByteBuf;
@@ -35,7 +36,10 @@
3536
import org.apache.bookkeeper.common.allocator.ByteBufAllocatorBuilder;
3637
import org.apache.bookkeeper.common.allocator.OutOfMemoryPolicy;
3738
import org.apache.bookkeeper.common.allocator.PoolingPolicy;
39+
import org.apache.bookkeeper.common.util.ShutdownUtil;
3840
import org.junit.Test;
41+
import org.mockito.MockedStatic;
42+
import org.mockito.Mockito;
3943

4044
/**
4145
* Tests for {@link ByteBufAllocatorBuilderImpl}.
@@ -87,6 +91,30 @@ public void testOomWithException() {
8791
assertEquals(outOfDirectMemException, receivedException.get());
8892
}
8993

94+
@Test()
95+
public void testOomExit() {
96+
ByteBufAllocator baseAlloc = mock(ByteBufAllocator.class);
97+
when(baseAlloc.directBuffer(anyInt(), anyInt())).thenThrow(outOfDirectMemException);
98+
99+
ByteBufAllocator alloc = ByteBufAllocatorBuilder.create()
100+
.pooledAllocator(baseAlloc)
101+
.outOfMemoryPolicy(OutOfMemoryPolicy.ThrowException)
102+
.exitOnOutOfMemory(true)
103+
.build();
104+
105+
MockedStatic<ShutdownUtil> mockedStatic = mockStatic(ShutdownUtil.class);
106+
107+
try {
108+
alloc.buffer();
109+
fail("Should have thrown exception");
110+
} catch (OutOfMemoryError e) {
111+
// Expected
112+
assertEquals(outOfDirectMemException, e);
113+
}
114+
115+
mockedStatic.verify(() -> ShutdownUtil.triggerImmediateForcefulShutdown(), Mockito.times(1));
116+
}
117+
90118
@Test
91119
public void testOomWithFallback() {
92120
ByteBufAllocator baseAlloc = mock(ByteBufAllocator.class);

bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieResources.java

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public static ByteBufAllocatorWithOomHandler createAllocator(ServerConfiguration
7070
.poolingConcurrency(conf.getAllocatorPoolingConcurrency())
7171
.outOfMemoryPolicy(conf.getAllocatorOutOfMemoryPolicy())
7272
.leakDetectionPolicy(conf.getAllocatorLeakDetectionPolicy())
73+
.exitOnOutOfMemory(conf.exitOnOutOfMemory())
7374
.build();
7475
}
7576

bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java

+1
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ public BookKeeper(ClientConfiguration conf, ZooKeeper zk, EventLoopGroup eventLo
478478
.poolingConcurrency(conf.getAllocatorPoolingConcurrency())
479479
.outOfMemoryPolicy(conf.getAllocatorOutOfMemoryPolicy())
480480
.leakDetectionPolicy(conf.getAllocatorLeakDetectionPolicy())
481+
.exitOnOutOfMemory(conf.exitOnOutOfMemory())
481482
.build();
482483
}
483484

bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/AbstractConfiguration.java

+10
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ public abstract class AbstractConfiguration<T extends AbstractConfiguration>
184184
protected static final String ALLOCATOR_POOLING_CONCURRENCY = "allocatorPoolingConcurrency";
185185
protected static final String ALLOCATOR_OOM_POLICY = "allocatorOutOfMemoryPolicy";
186186
protected static final String ALLOCATOR_LEAK_DETECTION_POLICY = "allocatorLeakDetectionPolicy";
187+
protected static final String ALLOCATOR_EXIT_ON_OUT_OF_MEMORY = "allocatorExitOnOutOfMemory";
187188

188189
// option to limit stats logging
189190
public static final String LIMIT_STATS_LOGGING = "limitStatsLogging";
@@ -1157,6 +1158,15 @@ public T setAllocatorLeakDetectionPolicy(LeakDetectionPolicy leakDetectionPolicy
11571158
return getThis();
11581159
}
11591160

1161+
public T setExitOnOutOfMemory(boolean exitOnOutOfMemory) {
1162+
this.setProperty(ALLOCATOR_EXIT_ON_OUT_OF_MEMORY, exitOnOutOfMemory);
1163+
return getThis();
1164+
}
1165+
1166+
public boolean exitOnOutOfMemory() {
1167+
return getBoolean(ALLOCATOR_EXIT_ON_OUT_OF_MEMORY, false);
1168+
}
1169+
11601170
/**
11611171
* Return whether the busy-wait is enabled for BookKeeper and Netty IO threads.
11621172
*

bookkeeper-server/src/test/java/org/apache/bookkeeper/conf/AbstractConfigurationTest.java

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package org.apache.bookkeeper.conf;
2020

2121
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertFalse;
23+
import static org.junit.Assert.assertTrue;
2224
import static org.mockito.Mockito.CALLS_REAL_METHODS;
2325
import static org.mockito.Mockito.mock;
2426

@@ -179,4 +181,12 @@ public void testAllocatorLeakDetectionPolicy() {
179181
System.getProperties().put(nettyLevelKey, nettyLevelStr);
180182
}
181183
}
184+
185+
@Test
186+
public void testExitOnOutOfMemory() {
187+
assertFalse(conf.exitOnOutOfMemory());
188+
conf.setExitOnOutOfMemory(true);
189+
assertTrue(conf.exitOnOutOfMemory());
190+
}
191+
182192
}

tools/perf/src/main/java/org/apache/bookkeeper/tools/perf/journal/JournalWriter.java

+1
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ private static ByteBufAllocator getAllocator(ServerConfiguration conf) {
495495
log.error("Unable to allocate memory, exiting bookie", ex);
496496
})
497497
.leakDetectionPolicy(conf.getAllocatorLeakDetectionPolicy())
498+
.exitOnOutOfMemory(conf.exitOnOutOfMemory())
498499
.build();
499500
}
500501

0 commit comments

Comments
 (0)