-
Notifications
You must be signed in to change notification settings - Fork 478
Expand file tree
/
Copy pathChronicleHashResources.java
More file actions
385 lines (351 loc) · 16.3 KB
/
Copy pathChronicleHashResources.java
File metadata and controls
385 lines (351 loc) · 16.3 KB
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
* Copyright 2013-2026 chronicle.software; SPDX-License-Identifier: Apache-2.0
*/
package net.openhft.chronicle.hash.impl;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.hash.ChronicleHashClosedException;
import net.openhft.chronicle.hash.impl.stage.hash.ChainingInterface;
import net.openhft.chronicle.hash.impl.util.Throwables;
import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import static net.openhft.chronicle.assertions.AssertUtil.SKIP_ASSERTIONS;
import static net.openhft.chronicle.map.internal.InternalAssertUtil.assertAddress;
import static net.openhft.chronicle.map.internal.InternalAssertUtil.assertPosition;
/**
* ChronicleHashResources is Runnable to be passed as "hunk" to {@link sun.misc.Cleaner}.
*/
public abstract class ChronicleHashResources implements Runnable {
private static final int OPEN = 0;
private static final int PARTIALLY_CLOSED = 1;
private static final int COMPLETELY_CLOSED = 2;
/**
* This field is volatile and {@link #closed()} accessor is not synchronized, because it
* is called on each {@link VanillaChronicleHash} access from all threads, synchronization would
* make {@code closed()} a bottleneck.
*/
private volatile int state = OPEN;
private List<MemoryResource> memoryResources = new ArrayList<>();
private List<Closeable> closeables = new ArrayList<>(1);
private List<WeakReference<ContextHolder>> contexts = new ArrayList<>();
/**
* Identity String of the ChronicleHash, for which this ChronicleHashResources is created.
* ChronicleHash couldn't be directly referenced, because {@code ChronicleHashResources} is a
* hunk for {@link sun.misc.Cleaner}, and it would prevent the chronicleHash from ever becoming
* unreachable.
*/
private String chronicleHashIdentityString;
List<WeakReference<ContextHolder>> contexts() {
return contexts;
}
final synchronized long totalMemory() {
if (closed())
return 0L;
long totalMemory = 0L;
//noinspection ForLoopReplaceableByForEach -- allocation-free looping
for (int i = 0; i < memoryResources.size(); i++) {
totalMemory += memoryResources.get(i).size;
}
return totalMemory;
}
final boolean closed() {
return state != OPEN;
}
private void checkOpen() {
if (closed())
throw new ChronicleHashClosedException(chronicleHashIdentityString);
}
/**
* Need to set {@link #chronicleHashIdentityString} via this setter rather than once in the
* constructor, because {@code ChronicleHashResources} instance is created before it's
* {@link net.openhft.chronicle.hash.ChronicleHash}.
*/
public final void setChronicleHashIdentityString(
String chronicleHashIdentityString) {
checkOpen();
synchronized (this) {
checkOpen();
this.chronicleHashIdentityString = chronicleHashIdentityString;
}
}
final void addMemoryResource(final long address,
final long size) {
assert SKIP_ASSERTIONS || assertAddress(address);
assert SKIP_ASSERTIONS || assertPosition(size);
checkOpen();
synchronized (this) {
checkOpen();
memoryResources.add(new MemoryResource(address, size));
}
}
final void addCloseable(Closeable closeable) {
checkOpen();
synchronized (this) {
checkOpen();
closeables.add(closeable);
}
}
final void addContext(ContextHolder contextHolder) {
checkOpen();
synchronized (this) {
checkOpen();
expungeStateContexts();
contexts.add(new WeakReference<>(contextHolder));
}
}
private void expungeStateContexts() {
contexts.removeIf(ref -> {
ContextHolder contextHolder = ref.get();
return contextHolder == null || !contextHolder.get().owner().isAlive();
});
}
/**
* This run() method is called from {@link sun.misc.Cleaner}, hence must not fail
*/
@Override
public void run() {
Throwable thrown = null;
try {
if (state == COMPLETELY_CLOSED)
return;
try {
Jvm.error().on(getClass(), chronicleHashIdentityString+" is not closed manually, cleaned up from Cleaner" );
} catch (Throwable t) {
thrown = t;
} finally {
synchronized (this) {
if (state == COMPLETELY_CLOSED) {
Jvm.error().on(getClass(), "Somebody closed "+chronicleHashIdentityString+" while it is processed by Cleaner, " +
"this should be impossible");
} else {
thrown = Throwables.returnOrSuppress(thrown, releaseEverything(true));
}
}
if (thrown != null) {
try {
Jvm.error().on(getClass(), "Error on releasing resources of " + chronicleHashIdentityString,
thrown);
} catch (Throwable t) {
// This may occur if we are in shutdown hooks, and the log service has
// already been shut down. Try to fall back to printStackTrace().
thrown.addSuppressed(t);
thrown.printStackTrace();
}
}
}
} catch (Throwable ignore) {
// Just don't fail anyway. We will have another attempt to close this ChronicleMap from
// ChronicleHashCloseOnExitHook.
}
}
abstract void releaseMemoryResource(MemoryResource memoryResource) throws IOException;
Throwable releaseExtraSystemResources() {
// nothing to release
return null;
}
public final boolean releaseManually() {
if (state == COMPLETELY_CLOSED)
return false;
// It's important to set state = PARTIALLY_CLOSED before calling closeContexts(), to ensure
// that threads which access this chronicleHash for the first time concurrently with
// chronicleHash.close() will either fail to register the context via addContext() (because
// checkOpen() is called there), or either the registered contexts will be visible and
// closed in closeContexts(). Also, it allows to not care about null elements of
// memoryResources, contexts and closeables lists in addMemoryResource(), addContext() and
// addCloseable() respectively.
state = PARTIALLY_CLOSED;
synchronized (this) {
if (state == COMPLETELY_CLOSED)
return false;
}
Throwable thrown = releaseEverything(false);
if (thrown != null)
throw Throwables.propagate(thrown);
return true;
}
private Throwable releaseEverything(boolean releaseFromCleaner) {
// Paranoiac mode: methods closeContexts(), closeCloseables(), releaseMemoryResources(),
// releaseExtraSystemResources() and Throwables.returnOrSuppress() should never throw any
// throwables (the first three should return them instead of throwing), but we don't trust
// ourselves and wrap every call into it's own try-finally block.
Throwable thrown = null;
try {
if (contexts != null)
thrown = closeContexts();
} catch (Throwable t) {
thrown = t;
} finally {
try {
if (closeables != null)
thrown = Throwables.returnOrSuppress(thrown, closeCloseables());
} catch (Throwable t) {
try {
thrown = Throwables.returnOrSuppress(thrown, t);
} catch (Throwable stackOverflowError) {
// It seems that only StackOverflowError could be thrown from
// Throwables.returnOrSuppress(), but in this case `thrown` and `t` are likely
// StackOverflowErrors too, so just return one of them without trying to call
// any more methods, including Throwable.addSuppressed().
thrown = thrown != null ? thrown : t;
}
}
}
// It's important to close the system resources only after contexts and closeables are
// closed successfully, because the latter may use those system resources. However if
// releaseEverything() is called from Cleaner, the ChronicleMap object is already
// unreachable, that means no contexts are accessing it anymore and the problem with closing
// contexts could probably be StackOverflowError or other exotic Error, so in this case we
// try to close the system resources anyway.
if (thrown == null || releaseFromCleaner) {
try {
thrown = releaseSystemResources(thrown);
} catch (Throwable stackOverflowError) {
// It seems that only StackOverflowError could be thrown (rather than returned!)
// from releaseSystemResources(), so just return any Throwable from
// releaseEverything() without trying to call any more methods.
thrown = thrown != null ? thrown : stackOverflowError;
}
}
if (thrown == null) {
state = COMPLETELY_CLOSED;
}
return thrown;
}
private Throwable releaseSystemResources(Throwable thrown) {
try {
if (memoryResources != null)
thrown = Throwables.returnOrSuppress(thrown, releaseMemoryResources());
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
} finally {
try {
thrown = Throwables.returnOrSuppress(thrown, releaseExtraSystemResources());
} catch (Throwable t) {
try {
thrown = Throwables.returnOrSuppress(thrown, t);
} catch (Throwable stackOverflowError) {
// It seems that only StackOverflowError could be thrown from
// Throwables.returnOrSuppress(), but in this case `thrown` and `t` are likely
// StackOverflowErrors too, so just return one of them without trying to call
// any more methods, including Throwable.addSuppressed().
thrown = thrown != null ? thrown : t;
}
}
}
return thrown;
}
private Throwable closeContexts() {
Throwable thrown = null;
List<WeakReference<ContextHolder>> contexts = this.contexts;
// Indexed loop instead of using Iterator because we may be in the context of cleaning up
// after OutOfMemoryError, and allocating Iterator object may lead to another
// OutOfMemoryError, so we try to avoid any allocations.
for (int i = 0; i < contexts.size(); i++) {
WeakReference<ContextHolder> contextHolderRef = contexts.get(i);
// The context reference could have already been set to null in (*). See comments in
// closeContext() with more explanations.
if (contextHolderRef != null) {
try {
ContextHolder contextHolder = contextHolderRef.get();
if (contextHolder != null) {
closeContext(contextHolder);
}
// (*) Make GC life easier
contexts.set(i, null);
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
}
}
}
// Forget about contexts only if all of them are successfully closed, i. e. no throwables
// were thrown in the above loop.
if (thrown == null) {
// Make GC life easier
this.contexts = null;
}
return thrown;
}
private void closeContext(ContextHolder contextHolder) {
ChainingInterface context = contextHolder.get();
// The context could have already been cleared, if this is the second attempt to close
// contexts, the first one failed e. g. with IllegalStateException on one of the contexts
// (see comment (*) below in this method), it could have succeed for some contexts and
// contextHolder.clear() is performed.
if (context != null) {
if (context.owner().isAlive()) {
// Ensures that if the thread owning this context will come to access chronicleHash
// concurrently with resource releasing operation, it will fail due to the check in
// context.lockContextLocally() method. If the thread owning this context is
// currently accessing chronicleHash, closeContext() will spin-wait until the end of
// this access session.
context.closeContext(chronicleHashIdentityString);
}
// (*) Don't execute contextHolder.clear() from a finally section of a try block
// wrapping context.closeContext(), because if context.closeContext() fails e. g. with
// IllegalStateException because the context is currently used (this happens if
// ChronicleMap.close() is called within try-with-resources block of it's own operation,
// see MapCloseTest.closeInContextTest()), we may want to try to close this context
// again from ChronicleHashCloseOnExitHook.
// See ContextHolder's class-level Javadoc comment that explains the motivation for
// calling contextHolder.clear().
contextHolder.clear();
}
}
private Throwable closeCloseables() {
Throwable thrown = null;
List<Closeable> closeables = this.closeables;
// Indexed loop instead of using Iterator because we may be in the context of cleaning up
// after OutOfMemoryError, and allocating Iterator object may lead to another
// OutOfMemoryError, so we try to avoid any allocations.
for (int i = 0; i < closeables.size(); i++) {
Closeable closeable = closeables.get(i);
// The closeable could have already been set to null in (*), see comments in
// closeContexts() method which has similar logic.
if (closeable != null) {
try {
closeable.close();
// (*) Make GC life easier
closeables.set(i, null);
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
}
}
}
// Forget about closeables only if all of them are successfully closed, i. e. no throwables
// were thrown in the above loop.
if (thrown == null) {
// Make GC life easier
this.closeables = null;
}
return thrown;
}
private Throwable releaseMemoryResources() {
Throwable thrown = null;
List<MemoryResource> memoryResources = this.memoryResources;
// Indexed loop instead of using Iterator because we may be in the context of cleaning up
// after OutOfMemoryError, and allocating Iterator object may lead to another
// OutOfMemoryError, so we try to avoid any allocations.
for (int i = 0; i < memoryResources.size(); i++) {
MemoryResource memoryResource = memoryResources.get(i);
// The memory resource could have already been nulled out in (*), see comments in
// closeCloseables() method which has similar logic.
if (memoryResource != null) {
try {
releaseMemoryResource(memoryResource);
// (*) Make GC life easier
memoryResources.set(i, null);
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
}
}
}
// Forget about memory resources only if all of them are successfully released, i. e. no
// throwables were thrown in the above loop.
if (thrown == null) {
this.memoryResources = null;
}
return thrown;
}
}