View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2026 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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  package org.newsclub.net.unix.memory;
19  
20  import java.io.FileDescriptor;
21  import java.io.IOException;
22  import java.lang.foreign.Arena;
23  import java.lang.foreign.MemorySegment;
24  import java.util.ArrayList;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.Objects;
30  import java.util.WeakHashMap;
31  
32  import org.newsclub.net.unix.CleanableState;
33  import org.newsclub.net.unix.MemoryImplUtilInternal;
34  
35  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
36  
37  class SharedMemoryCleaner extends CleanableState {
38    private final Map<MemorySegment, Integer> segments = new LinkedHashMap<>();
39    private final Map<Futex, Futex> futexes = new WeakHashMap<>();
40    private Arena arena;
41    private final boolean closeArena;
42    private MemorySegment arenaSegment;
43    final FileDescriptor fd;
44  
45    SharedMemoryCleaner(Arena arena, Object observed, FileDescriptor fd) {
46      super(observed);
47      if (arena == null) {
48        this.arena = null;
49        this.closeArena = true;
50      } else {
51        this.arena = arena;
52        this.closeArena = false;
53      }
54      this.fd = fd;
55    }
56  
57    void registerMemorySegment(MemorySegment ms, int duplicates) {
58      synchronized (segments) {
59        segments.put(ms, duplicates);
60      }
61    }
62  
63    @Override
64    @SuppressWarnings("PMD.CognitiveComplexity")
65    @SuppressFBWarnings("USO_UNSAFE_OBJECT_SYNCHRONIZATION")
66    protected synchronized void doClean() throws IOException {
67      if (!SharedMemory.isUtilLoaded()) {
68        // Nothing to do
69        return;
70      }
71  
72      Map<FileDescriptor, Long> map = SharedMemory.FD_MEMORY;
73      if (map != null && fd != null) {
74        synchronized (map) {
75          map.remove(fd);
76        }
77      }
78  
79      synchronized (futexes) {
80        if (!futexes.isEmpty()) {
81          for (Futex f : futexes.keySet()) {
82            try {
83              f.tryWake(true); // unblock waiting threads
84              f.close();
85            } catch (Exception e) { // NOPMD
86              // ignore
87            }
88          }
89          futexes.clear();
90        }
91      }
92  
93      if (closeArena && arena != null) {
94        arena.close();
95      }
96  
97      MemoryImplUtilInternal util = SharedMemory.getUtil();
98  
99      IOException exc = null;
100     if (fd != null && fd.valid()) {
101       try {
102         util.close(fd);
103       } catch (IOException e) {
104         exc = e;
105       }
106     }
107 
108     List<Entry<MemorySegment, Integer>> list;
109     synchronized (segments) {
110       list = new ArrayList<>(segments.entrySet()).reversed();
111       segments.clear();
112     }
113     for (Map.Entry<MemorySegment, Integer> en : list) {
114       MemorySegment ms = en.getKey();
115       int duplicates = en.getValue();
116 
117       long addr = ms.address();
118       long length = ms.byteSize();
119       if (ms.scope().isAlive()) {
120         util.madvise(addr, length, MemoryImplUtilInternal.MADV_FREE_NOW, true);
121         continue;
122       }
123 
124       try {
125         util.unmap(addr, length, duplicates, false);
126       } catch (IOException e) {
127         if (exc == null) {
128           exc = e;
129         } else {
130           exc.addSuppressed(e);
131         }
132       }
133     }
134 
135     if (exc != null) {
136       throw exc;
137     }
138   }
139 
140   public boolean isCovered(MemorySegment segment) {
141     Objects.requireNonNull(segment);
142     long start = segment.address();
143     long end = start + segment.byteSize();
144     synchronized (segments) {
145       for (MemorySegment ms : segments.keySet()) {
146         if (ms == segment) { // NOPMD
147           return true;
148         }
149         long addr = ms.address();
150         if (start >= addr && end <= addr + ms.byteSize()) {
151           return true;
152         }
153       }
154     }
155     return false;
156   }
157 
158   public synchronized MemorySegment getArenaSegment() {
159     if (this.arenaSegment == null) {
160       this.arenaSegment = getArena().allocate(0);
161     }
162     return arenaSegment;
163   }
164 
165   public void registerFutex(Futex futex) {
166     synchronized (futexes) {
167       futexes.put(futex, futex);
168     }
169   }
170 
171   public void checkCovered(MemorySegment addr) throws IOException {
172     if (!isCovered(addr)) {
173       throw new IOException("Not a MemorySegment of ours");
174     }
175   }
176 
177   public synchronized Arena getArena() {
178     if (this.arena == null) {
179       this.arena = Arena.ofShared();
180     }
181     return this.arena;
182   }
183 }