Skip to content

Commit 75663c0

Browse files
committed
memory: Improve SharedMemory handling, cleaner
1 parent e287ded commit 75663c0

2 files changed

Lines changed: 73 additions & 39 deletions

File tree

junixsocket-memory/src/main/java/org/newsclub/net/unix/memory/SharedMemory.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private SharedMemory(FileDescriptor fd, long size, String name, int mopts) throw
102102
this.size = size;
103103
}
104104
Objects.requireNonNull(fd);
105-
this.cleaner = new SharedMemoryCleaner(this, fd);
105+
this.cleaner = new SharedMemoryCleaner(null, this, fd);
106106
this.name = name;
107107
this.knownReadOnly = isReadOnly(mopts);
108108
this.unlinkUponClose = (mopts & MemoryImplUtilInternal.MOPT_UNLINK_UPON_CLOSE) != 0;
@@ -459,11 +459,12 @@ public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena, long of
459459
throw new IllegalArgumentException("length");
460460
}
461461

462+
// use a zero-length, 0-address segment for lifecycle management, preventing chicken-egg problem
463+
MemorySegment arenaSegment;
462464
if (arena == null) {
463-
arena = cleaner.defaultArena;
464-
if (arena == null) {
465-
arena = cleaner.defaultArena = Arena.ofShared();
466-
}
465+
arenaSegment = cleaner.getArenaSegment();
466+
} else {
467+
arenaSegment = arena.allocate(0);
467468
}
468469

469470
int mmode = resolveMmode(mapMode);
@@ -474,11 +475,19 @@ public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena, long of
474475
length = size;
475476
}
476477

477-
// use a zero-length, 0-address segment for lifecycle management, preventing chicken-egg problem
478-
MemorySegment arenaSegment = arena.allocate(0);
478+
ByteBuffer buf = getUtil().mmapShm(arenaSegment, cleaner.fd, offset, length, mmode, duplicates);
479+
return asRegisteredMemorySegment(cleaner, buf, (mmode
480+
& MemoryImplUtilInternal.MMODE_WRITE) != 0, duplicates);
481+
}
482+
483+
static MemorySegment asRegisteredMemorySegment(SharedMemoryCleaner cleaner, ByteBuffer buf,
484+
boolean rw) {
485+
return asRegisteredMemorySegment(cleaner, buf, rw, 0);
486+
}
479487

480-
ByteBuffer buf = getUtil().mmap(arenaSegment, cleaner.fd, offset, length, mmode, duplicates);
481-
if ((mmode & MemoryImplUtilInternal.MMODE_WRITE) == 0) {
488+
private static MemorySegment asRegisteredMemorySegment(SharedMemoryCleaner cleaner,
489+
ByteBuffer buf, boolean rw, int duplicates) {
490+
if (!rw) {
482491
// The MapMode is read-only.
483492
// If we don't ask for a read-only buffer here, write accesses will fail with a page fault
484493
// ("java.lang.InternalError: a fault occurred in an unsafe memory access operation")

junixsocket-memory/src/main/java/org/newsclub/net/unix/memory/SharedMemoryCleaner.java

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
import java.io.IOException;
2222
import java.lang.foreign.Arena;
2323
import java.lang.foreign.MemorySegment;
24+
import java.util.ArrayList;
2425
import java.util.LinkedHashMap;
26+
import java.util.List;
2527
import java.util.Map;
28+
import java.util.Map.Entry;
2629
import java.util.Objects;
2730
import java.util.WeakHashMap;
2831

@@ -32,13 +35,24 @@
3235
class SharedMemoryCleaner extends CleanableState {
3336
private final Map<MemorySegment, Integer> segments = new LinkedHashMap<>();
3437
private final Map<Futex, Futex> futexes = new WeakHashMap<>();
35-
private final Arena arena = Arena.ofShared();
36-
private final MemorySegment arenaSegment = arena.allocate(0);
38+
private Arena arena;
39+
private final boolean closeArena;
40+
private MemorySegment arenaSegment;
3741
final FileDescriptor fd;
38-
Arena defaultArena;
3942

40-
protected SharedMemoryCleaner(Object observed, FileDescriptor fd) {
43+
SharedMemoryCleaner(Arena arena, Object observed) {
44+
this(arena, observed, null);
45+
}
46+
47+
SharedMemoryCleaner(Arena arena, Object observed, FileDescriptor fd) {
4148
super(observed);
49+
if (arena == null) {
50+
this.arena = null;
51+
this.closeArena = true;
52+
} else {
53+
this.arena = arena;
54+
this.closeArena = false;
55+
}
4256
this.fd = fd;
4357
}
4458

@@ -50,14 +64,14 @@ void registerMemorySegment(MemorySegment ms, int duplicates) {
5064

5165
@Override
5266
@SuppressWarnings("PMD.CognitiveComplexity")
53-
protected void doClean() throws IOException {
67+
protected synchronized void doClean() throws IOException {
5468
if (!SharedMemory.isUtilLoaded()) {
5569
// Nothing to do
5670
return;
5771
}
5872

5973
Map<FileDescriptor, Long> map = SharedMemory.FD_MEMORY;
60-
if (map != null) {
74+
if (map != null && fd != null) {
6175
synchronized (map) {
6276
map.remove(fd);
6377
}
@@ -77,45 +91,46 @@ protected void doClean() throws IOException {
7791
}
7892
}
7993

80-
if (defaultArena != null) {
81-
defaultArena.close();
82-
defaultArena = null;
94+
if (closeArena && arena != null) {
95+
arena.close();
8396
}
8497

8598
MemoryImplUtilInternal util = SharedMemory.getUtil();
8699

87100
IOException exc = null;
88-
if (fd.valid()) {
101+
if (fd != null && fd.valid()) {
89102
try {
90103
util.close(fd);
91104
} catch (IOException e) {
92105
exc = e;
93106
}
94107
}
95108

109+
List<Entry<MemorySegment, Integer>> list;
96110
synchronized (segments) {
97-
for (Map.Entry<MemorySegment, Integer> en : segments.entrySet()) {
98-
MemorySegment ms = en.getKey();
99-
int duplicates = en.getValue();
100-
101-
long addr = ms.address();
102-
long length = ms.byteSize();
103-
if (ms.scope().isAlive()) {
104-
util.madvise(addr, length, MemoryImplUtilInternal.MADV_FREE_NOW, true);
105-
continue;
106-
}
111+
list = new ArrayList<>(segments.entrySet()).reversed();
112+
segments.clear();
113+
}
114+
for (Map.Entry<MemorySegment, Integer> en : list) {
115+
MemorySegment ms = en.getKey();
116+
int duplicates = en.getValue();
117+
118+
long addr = ms.address();
119+
long length = ms.byteSize();
120+
if (ms.scope().isAlive()) {
121+
util.madvise(addr, length, MemoryImplUtilInternal.MADV_FREE_NOW, true);
122+
continue;
123+
}
107124

108-
try {
109-
util.unmap(addr, length, duplicates, false);
110-
} catch (IOException e) {
111-
if (exc == null) {
112-
exc = e;
113-
} else {
114-
exc.addSuppressed(e);
115-
}
125+
try {
126+
util.unmap(addr, length, duplicates, false);
127+
} catch (IOException e) {
128+
if (exc == null) {
129+
exc = e;
130+
} else {
131+
exc.addSuppressed(e);
116132
}
117133
}
118-
segments.clear();
119134
}
120135

121136
if (exc != null) {
@@ -141,7 +156,10 @@ public boolean isCovered(MemorySegment segment) {
141156
return false;
142157
}
143158

144-
public MemorySegment getArenaSegment() {
159+
public synchronized MemorySegment getArenaSegment() {
160+
if (this.arenaSegment == null) {
161+
this.arenaSegment = getArena().allocate(0);
162+
}
145163
return arenaSegment;
146164
}
147165

@@ -156,4 +174,11 @@ public void checkCovered(MemorySegment addr) throws IOException {
156174
throw new IOException("Not a MemorySegment of ours");
157175
}
158176
}
177+
178+
public synchronized Arena getArena() {
179+
if (this.arena == null) {
180+
this.arena = Arena.ofShared();
181+
}
182+
return this.arena;
183+
}
159184
}

0 commit comments

Comments
 (0)