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.Closeable;
21  import java.io.FileDescriptor;
22  import java.io.IOException;
23  import java.lang.foreign.Arena;
24  import java.lang.foreign.MemorySegment;
25  import java.nio.ByteBuffer;
26  import java.nio.channels.ClosedChannelException;
27  import java.nio.channels.FileChannel;
28  import java.nio.channels.FileChannel.MapMode;
29  import java.nio.file.attribute.PosixFilePermission;
30  import java.nio.file.attribute.PosixFilePermissions;
31  import java.util.Collections;
32  import java.util.Map;
33  import java.util.Objects;
34  import java.util.Set;
35  import java.util.WeakHashMap;
36  
37  import org.newsclub.net.unix.AFSocket;
38  import org.newsclub.net.unix.FileChannelSupplier;
39  import org.newsclub.net.unix.FileDescriptorCast;
40  import org.newsclub.net.unix.MemoryImplUtilInternal;
41  
42  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
43  
44  /**
45   * Some shared memory.
46   *
47   * @author Christian Kohlschütter
48   */
49  @SuppressFBWarnings("OVERLY_PERMISSIVE_FILE_PERMISSION")
50  public final class SharedMemory implements Closeable {
51    private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS = PosixFilePermissions
52        .fromString("rw-rw-rw-");
53  
54    /**
55     * Keep track of known shared memory sizes, but only if useful (currently: Windows only).
56     */
57    static Map<FileDescriptor, Long> FD_MEMORY; // NOPMD
58  
59    /**
60     * The exact size (in bytes) required for a {@link MemorySegment} used in
61     * {@link #mutex(MemorySegment)}.
62     */
63    public static final int MUTEX_SEGMENT_SIZE = 8;
64  
65    /**
66     * The exact size (in bytes) required for a {@link MemorySegment} used in
67     * {@link #futex(MemorySegment)}.
68     */
69    static final int FUTEX32_SEGMENT_SIZE = 4;
70  
71    private static final Map<String, Integer> MAP_MODES = Map.of(//
72        "READ_ONLY", MemoryImplUtilInternal.MMODE_READ, //
73        "READ_WRITE", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_WRITE), //
74        "PRIVATE", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_WRITE
75            | MemoryImplUtilInternal.MMODE_COPY_ON_WRITE), //
76        // from ExtendedMapMode:
77        "READ_ONLY_SYNC", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_SYNC), //
78        "READ_WRITE_SYNC", (MemoryImplUtilInternal.MMODE_READ | MemoryImplUtilInternal.MMODE_WRITE
79            | MemoryImplUtilInternal.MMODE_SYNC) //
80    );
81  
82    private final String name;
83  
84    private final boolean knownReadOnly;
85    private final boolean unlinkUponClose;
86  
87    static MemoryImplUtilInternal UTIL = null; // NOPMD
88  
89    private final SharedMemoryCleaner cleaner;
90  
91    private final long size;
92  
93    private SharedMemory(FileDescriptor fd) throws IOException {
94      this(fd, -1, null, 0);
95    }
96  
97    private SharedMemory(FileDescriptor fd, long size, String name, int mopts) throws IOException {
98      super();
99      if (size == -1) {
100       this.size = determineSize(fd);
101     } else {
102       this.size = size;
103     }
104     Objects.requireNonNull(fd);
105     this.cleaner = new SharedMemoryCleaner(null, this, fd);
106     this.name = name;
107     this.knownReadOnly = isReadOnly(mopts);
108     this.unlinkUponClose = (mopts & MemoryImplUtilInternal.MOPT_UNLINK_UPON_CLOSE) != 0;
109   }
110 
111   private static long determineSize(FileDescriptor fd) throws IOException {
112     Map<FileDescriptor, Long> map = FD_MEMORY;
113     if (map != null) {
114       synchronized (map) {
115         Long knownSize = map.get(fd);
116         if (knownSize != null) {
117           return knownSize;
118         }
119       }
120     }
121     return getUtil().sizeOfSharedMemory(fd);
122     // ; return FileDescriptorCast.using(fd).as(FileChannel.class).size();
123   }
124 
125   static boolean isUtilLoaded() {
126     return UTIL != null;
127   }
128 
129   static MemoryImplUtilInternal getUtil() {
130     AFSocket.isSupported(); // trigger init
131 
132     MemoryImplUtilInternal util = UTIL;
133     if (util == null) {
134       throw new IllegalStateException("MemoryImplUtilInternal not initialized");
135     }
136     return util;
137   }
138 
139   /**
140    * Internal initializer used by junixsocket-common; do not use.
141    *
142    * @param util The MemoryImplUtil instance.
143    */
144   public static synchronized void init(MemoryImplUtilInternal util) {
145     if (util != null) {
146       if (UTIL == null) {
147         UTIL = util;
148         if (util.needToTrackSharedMemory()) {
149           FD_MEMORY = new WeakHashMap<>();
150         } else {
151           FD_MEMORY = null;
152         }
153       } else {
154         throw new IllegalStateException();
155       }
156     }
157   }
158 
159   /**
160    * Creates a new {@link SharedMemory} instance using the given file descriptor, which can be
161    * associated with a regular file that is to be memory-mapped, or a shared memory region.
162    *
163    * @param fd The file descriptor.
164    * @return The new instance.
165    * @throws IOException on error.
166    */
167   public static SharedMemory using(FileDescriptor fd) throws IOException {
168     return new SharedMemory(fd);
169   }
170 
171   /**
172    * Creates a new {@link SharedMemory} instance under the given name, using default permissions
173    * (read-write for all users, where applicable). If there already exists an object under that
174    * name, this call fails with an error.
175    *
176    * @param name The name.
177    * @param minimumLength The requested length (the actual object can be larger).
178    * @param options Instantiation options.
179    * @return The new instance.
180    * @throws IOException on error.
181    */
182   public static SharedMemory createExclusively(String name, long minimumLength,
183       SharedMemoryOption... options) throws IOException {
184     return createExclusively(name, minimumLength, DEFAULT_PERMISSIONS, options);
185   }
186 
187   /**
188    * Creates a new {@link SharedMemory} instance under the given name. If there already exists an
189    * instance under that name, this call fails with an error.
190    *
191    * @param name The name.
192    * @param minimumLength The requested length (the actual object can be larger).
193    * @param perms The file system permissions, where applicable.
194    * @param options Instantiation options.
195    * @return The new instance.
196    * @throws IOException on error.
197    */
198   public static SharedMemory createExclusively(String name, long minimumLength,
199       Set<PosixFilePermission> perms, SharedMemoryOption... options) throws IOException {
200     return shmOpen(name, perms, toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT
201         | MemoryImplUtilInternal.MOPT_EXCL, minimumLength);
202   }
203 
204   /**
205    * Creates a new {@link SharedMemory} instance under the given name, using default permissions
206    * (read-write for all users, where applicable). If there already exists an object under that
207    * name, that object is opened instead.
208    *
209    * @param name The name.
210    * @param minimumLength The requested length (the actual object can be larger).
211    * @param options Instantiation options.
212    * @return The new instance.
213    * @throws IOException on error.
214    */
215   public static SharedMemory createOrOpenExisting(String name, long minimumLength,
216       SharedMemoryOption... options) throws IOException {
217     return createOrOpenExisting(name, minimumLength, DEFAULT_PERMISSIONS, options);
218   }
219 
220   /**
221    * Creates a new {@link SharedMemory} instance under the given namex. If there already exists an
222    * object under that name, that object is opened instead.
223    *
224    * @param name The name.
225    * @param minimumLength The requested length (the actual object can be larger).
226    * @param perms The file system permissions, where applicable.
227    * @param options Instantiation options.
228    * @return The new instance.
229    * @throws IOException on error.
230    */
231   public static SharedMemory createOrOpenExisting(String name, long minimumLength,
232       Set<PosixFilePermission> perms, SharedMemoryOption... options) throws IOException {
233     return shmOpen(name, perms, toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT,
234         minimumLength);
235   }
236 
237   /**
238    * Creates a new {@link SharedMemory} instance under the given name, using default permissions
239    * (read-write for all users, where applicable). If there already exists an object under that
240    * name, that object is reused (truncated to zero or deleted prior to allocation).
241    *
242    * @param name The name.
243    * @param minimumLength The requested length (the actual object can be larger).
244    * @param options Instantiation options.
245    * @return The new instance.
246    * @throws IOException on error.
247    */
248   public static SharedMemory createOrReuseExisting(String name, long minimumLength,
249       SharedMemoryOption... options) throws IOException {
250     return createOrReuseExisting(name, minimumLength, DEFAULT_PERMISSIONS, options);
251   }
252 
253   /**
254    * Creates a new {@link SharedMemory} instance under the given name. If there already exists an
255    * object under that name, that object is reused (truncated to zero or deleted prior to
256    * allocation).
257    *
258    * @param name The name.
259    * @param minimumLength The requested length (the actual object can be larger).
260    * @param perms The file system permissions, where applicable.
261    * @param options Instantiation options.
262    * @return The new instance.
263    * @throws IOException on error.
264    */
265   public static SharedMemory createOrReuseExisting(String name, long minimumLength,
266       Set<PosixFilePermission> perms, SharedMemoryOption... options) throws IOException {
267     return shmOpen(name, perms, toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT
268         | MemoryImplUtilInternal.MOPT_TRUNC, minimumLength);
269   }
270 
271   /**
272    * Creates a new {@link SharedMemory} instance under the given name, using the object under the
273    * given name. This call fails with an exception if no such object exists.
274    *
275    * @param name The name.
276    * @param options Instantiation options.
277    * @return The new instance.
278    * @throws IOException on error.
279    */
280   public static SharedMemory openExisting(String name, SharedMemoryOption... options)
281       throws IOException {
282     return shmOpen(name, DEFAULT_PERMISSIONS, toOptions(options), 0);
283   }
284 
285   /**
286    * Creates a new {@link SharedMemory} instance using an anonymous identifier.
287    *
288    * @param minimumLength The requested length (the actual object can be larger).
289    * @return The new instance.
290    * @throws IOException on error.
291    */
292   public static SharedMemory createAnonymous(long minimumLength) throws IOException {
293     return createAnonymous(minimumLength, (SharedMemoryOption[]) null);
294   }
295 
296   /**
297    * Creates a new {@link SharedMemory} instance using an anonymous identifier.
298    *
299    * @param minimumLength The requested length (the actual object can be larger).
300    * @param options Instantiation options.
301    * @return The new instance.
302    * @throws IOException on error.
303    */
304   public static SharedMemory createAnonymous(long minimumLength, SharedMemoryOption... options)
305       throws IOException {
306     int mopts = toOptions(options) | MemoryImplUtilInternal.MOPT_CREAT
307         | MemoryImplUtilInternal.MOPT_TRUNC;
308     return shmOpen0(null, Collections.emptySet(), mopts, minimumLength);
309   }
310 
311   private static SharedMemory shmOpen(String name, Set<PosixFilePermission> perms, int mopts,
312       long minimumLength) throws IOException {
313     name = checkShmName(name);
314     return shmOpen0(name, perms, mopts, minimumLength);
315   }
316 
317   @SuppressFBWarnings("USO_UNSAFE_ACCESSIBLE_OBJECT_SYNCHRONIZATION")
318   private static SharedMemory shmOpen0(String name, Set<PosixFilePermission> perms, int mopts,
319       long minimumLength) throws IOException {
320     MemoryImplUtilInternal util = getUtil();
321 
322     FileDescriptor fd = new FileDescriptor();
323     long size = util.shmOpen(fd, name, minimumLength, toMode(perms), mopts);
324     SharedMemory sm = new SharedMemory(fd, size, name, mopts);
325 
326     Map<FileDescriptor, Long> map = FD_MEMORY;
327     if (map != null) {
328       synchronized (map) {
329         map.put(fd, size);
330       }
331     }
332     return sm;
333   }
334 
335   private static boolean isReadOnly(int mopts) {
336     return (mopts & MemoryImplUtilInternal.MOPT_RDONLY) != 0;
337   }
338 
339   /**
340    * Asks to explicitly unlink/remove a shared memory object identified by the given name.
341    * <p>
342    * This call may silently fail (some platforms do not support explicit unlinking -- they cleanup
343    * the objects automatically).
344    *
345    * @param name The name of the object that should be unlinked.
346    * @throws IOException on error.
347    */
348   public static void unlinkShared(String name) throws IOException {
349     getUtil().shmUnlink(checkShmName(name));
350   }
351 
352   /**
353    * Returns the file descriptor associated with this instance.
354    *
355    * @return The file descriptor.
356    */
357   public FileDescriptor getFileDescriptor() {
358     return cleaner.fd;
359   }
360 
361   /**
362    * Returns a {@link FileChannel} instance that can be used for memory-mapping via {code
363    * FileChannel#map}. There are no guarantees that writing/truncating/mapping works, however
364    * getting the current allocation size via {@link FileChannel#size()} should work.
365    *
366    * @return The {@link FileChannel}.
367    * @throws IOException on error.
368    * @throws ClosedChannelException if the file descriptor is closed.
369    * @throws UnsupportedOperationException if this operation is not supported on this platform.
370    */
371   FileChannel asMappableFileChannel() throws IOException {
372     if (!cleaner.fd.valid()) {
373       throw new ClosedChannelException();
374     }
375     if (FD_MEMORY != null) {
376       throw new UnsupportedOperationException();
377     }
378     if (knownReadOnly) {
379       return FileDescriptorCast.using(cleaner.fd).as(FileChannelSupplier.ReadOnly.class).get();
380     } else {
381       return FileDescriptorCast.using(cleaner.fd).as(FileChannel.class);
382     }
383   }
384 
385   /**
386    * Return a {@link MemorySegment} instance corresponding to this shared memory object, using the
387    * given {@link MapMode}, and a custom shared {@link Arena} that will be closed upon
388    * {@link SharedMemory#close()}.
389    *
390    * @param mapMode The map mode.
391    * @return The memory segment.
392    * @throws IOException on error.
393    */
394   public MemorySegment asMappedMemorySegment(MapMode mapMode) throws IOException {
395     return asMappedMemorySegment(mapMode, null, 0);
396   }
397 
398   /**
399    * Return a {@link MemorySegment} instance corresponding to this shared memory object, using the
400    * given {@link MapMode}, and the given arena.
401    * <p>
402    * If the given arena is {@code null}, a custom shared {@link Arena} is used that will be closed
403    * upon {@link SharedMemory#close()}.
404    *
405    * @param mapMode The map mode.
406    * @param arena The arena to use, or {@code null}.
407    * @return The memory segment.
408    * @throws IOException on error.
409    */
410   public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena) throws IOException {
411     return asMappedMemorySegment(mapMode, arena, 0);
412   }
413 
414   /**
415    * Return a {@link MemorySegment} instance corresponding to this shared memory object -- repeated
416    * multiple times after each other (aligned with page size) -- using the given {@link MapMode},
417    * and the given arena, as well as the duplication count.
418    * <p>
419    * This method is particularly useful to simplify building circular buffers ("magic RingBuffer").
420    * <p>
421    * If the given arena is {@code null}, a custom shared {@link Arena} is used that will be closed
422    * upon {@link SharedMemory#close()}.
423    *
424    * @param mapMode The map mode.
425    * @param arena The arena to use, or {@code null}.
426    * @param duplicates The number of times the shared memory should be repeated (0 = no repetitions,
427    *          just 1 copy).
428    * @return The memory segment.
429    * @throws IOException on error.
430    */
431   public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena, int duplicates)
432       throws IOException {
433     return asMappedMemorySegment(mapMode, arena, 0, -1, duplicates);
434   }
435 
436   /**
437    * Return a {@link MemorySegment} instance corresponding to a range of this shared memory object
438    * -- repeated multiple times after each other (aligned with page size) -- using the given
439    * {@link MapMode}, and the given arena, as well as the duplication count.
440    * <p>
441    * This method is particularly useful to simplify building circular buffers ("magic RingBuffer").
442    * <p>
443    * If the given arena is {@code null}, a custom shared {@link Arena} is used that will be closed
444    * upon {@link SharedMemory#close()}.
445    *
446    * @param mapMode The map mode.
447    * @param arena The arena to use, or {@code null}.
448    * @param offset The offset from the beginning of this segment, in bytes.
449    * @param length The length of the mapped region, in bytes.
450    * @param duplicates The number of times the shared memory should be repeated (0 = no repetitions,
451    *          just 1 copy).
452    * @return The memory segment.
453    * @throws IOException on error.
454    */
455   public MemorySegment asMappedMemorySegment(MapMode mapMode, Arena arena, long offset, long length,
456       int duplicates) throws IOException {
457     if (offset < 0) {
458       throw new IllegalArgumentException("startOffset");
459     } else if (length < -1) {
460       throw new IllegalArgumentException("length");
461     }
462 
463     // use a zero-length, 0-address segment for lifecycle management, preventing chicken-egg problem
464     MemorySegment arenaSegment;
465     if (arena == null) {
466       arenaSegment = cleaner.getArenaSegment();
467     } else {
468       arenaSegment = arena.allocate(0);
469     }
470 
471     int mmode = resolveMmode(mapMode);
472     if (length == -1) {
473       // FileChannel fc = asMappableFileChannel();
474       // long size = fc.size();
475 
476       length = size;
477     }
478 
479     ByteBuffer buf = getUtil().mmapShm(arenaSegment, cleaner.fd, offset, length, mmode, duplicates);
480     return asRegisteredMemorySegment(cleaner, buf, (mmode
481         & MemoryImplUtilInternal.MMODE_WRITE) != 0, duplicates);
482   }
483 
484   static MemorySegment asRegisteredMemorySegment(SharedMemoryCleaner cleaner, ByteBuffer buf,
485       boolean rw) {
486     return asRegisteredMemorySegment(cleaner, buf, rw, 0);
487   }
488 
489   private static MemorySegment asRegisteredMemorySegment(SharedMemoryCleaner cleaner,
490       ByteBuffer buf, boolean rw, int duplicates) {
491     if (!rw) {
492       // The MapMode is read-only.
493       // If we don't ask for a read-only buffer here, write accesses will fail with a page fault
494       // ("java.lang.InternalError: a fault occurred in an unsafe memory access operation")
495       buf = buf.asReadOnlyBuffer();
496     }
497     MemorySegment ms = MemorySegment.ofBuffer(buf);
498     cleaner.registerMemorySegment(ms, duplicates);
499     return ms;
500   }
501 
502   /**
503    * Adds the given {@link MemorySeal}s, preventing certain operations on shared memory.
504    *
505    * @param seals The seals.
506    * @throws IOException on error (e.g., if unsupported).
507    */
508   @SuppressWarnings("DoNotCallSuggester") // ErrorProne
509   public void addSeals(Set<MemorySeal> seals) throws IOException {
510     throw new IOException("Unsupported"); // FIXME
511   }
512 
513   /**
514    * Returns the current {@link MemorySeal}s for this shared memory instance.
515    *
516    * @return The seals, or empty if none or unsupported.
517    * @throws IOException on error (e.g., if a system call fails unexpectedly).
518    */
519   public Set<MemorySeal> getSeals() throws IOException {
520     return Collections.emptySet(); // FIXME
521   }
522 
523   private static String checkShmName(String name) {
524     Objects.requireNonNull(name);
525     if (name.length() == 0) {
526       throw new IllegalArgumentException("Name cannot be empty");
527     }
528     if (name.indexOf('/', 1) != -1) {
529       throw new IllegalArgumentException("Name must not contain extra slashes");
530     }
531     if (name.charAt(0) == '/') {
532       return name;
533     } else {
534       return "/" + name;
535     }
536   }
537 
538   private static int toMode(Set<PosixFilePermission> perms) {
539     int mode = 0;
540     if (perms == null) {
541       perms = DEFAULT_PERMISSIONS;
542     }
543     for (PosixFilePermission perm : perms) {
544       switch (perm) {
545         case OWNER_READ:
546           mode |= MemoryImplUtilInternal.S_IRUSR;
547           break;
548         case OWNER_WRITE:
549           mode |= MemoryImplUtilInternal.S_IWUSR;
550           break;
551         case GROUP_READ:
552           mode |= MemoryImplUtilInternal.S_IRGRP;
553           break;
554         case GROUP_WRITE:
555           mode |= MemoryImplUtilInternal.S_IWGRP;
556           break;
557         case OTHERS_READ:
558           mode |= MemoryImplUtilInternal.S_IROTH;
559           break;
560         case OTHERS_WRITE:
561           mode |= MemoryImplUtilInternal.S_IWOTH;
562           break;
563         default:
564           throw new IllegalArgumentException("Unsupported permission: " + perm);
565       }
566     }
567     return mode;
568   }
569 
570   private static int toOptions(SharedMemoryOption[] options) {
571     int opt = 0;
572     if (options != null) {
573       for (SharedMemoryOption option : options) {
574         opt |= option.getOpt();
575       }
576     }
577     return opt;
578   }
579 
580   /**
581    * Closes this {@link SharedMemory} resource, potentially unlinking the corresponding underlying
582    * resource from the kernel if the object has been instantiated with
583    * {@link SharedMemoryOption#UNLINK_UPON_CLOSE}.
584    */
585   @Override
586   public void close() throws IOException {
587     boolean valid = cleaner.fd.valid();
588 
589     cleaner.close();
590     if (unlinkUponClose && valid && name != null) {
591       MemoryImplUtilInternal util = getUtil();
592       util.shmUnlink(name);
593     }
594   }
595 
596   String getName() {
597     return name;
598   }
599 
600   private static int resolveMmode(MapMode mapMode) {
601     String modeString = mapMode.toString();
602     Integer mmode = MAP_MODES.get(modeString);
603     if (mmode == null) {
604       throw new UnsupportedOperationException("MapMode");
605     }
606     return mmode;
607   }
608 
609   /**
610    * Returns the system's default memory page allocation size for shared memory.
611    * <p>
612    * This may be larger than the system's regular page size (e.g., on Windows it's 64k).
613    *
614    * @return The page size.
615    */
616   public static long defaultAllocationSize() {
617     return getUtil().getSharedMemoryAllocationSize();
618   }
619 
620   Futex futex(MemorySegment addr) throws IOException {
621     return futex(addr, false, false);
622   }
623 
624   Futex futex(MemorySegment addr, boolean wakeUpOnClose) throws IOException {
625     return futex(addr, wakeUpOnClose, false);
626   }
627 
628   Futex futex(MemorySegment addr, boolean wakeUpOnClose, boolean zeroValueOnClose)
629       throws IOException {
630     if (addr.isReadOnly()) {
631       throw new IOException("MemorySegment is read-only");
632     }
633     cleaner.checkCovered(addr);
634     Futex futex = new Futex32(addr, zeroValueOnClose);
635     if (wakeUpOnClose) {
636       cleaner.registerFutex(futex);
637     }
638     return futex;
639   }
640 
641   /**
642    * Returns a {@link SharedMutex} instance working with the given {@link MemorySegment}, which has
643    * to be exactly {@link #MUTEX_SEGMENT_SIZE} bytes long.
644    *
645    * @param addr The address.
646    * @return The instance.
647    * @throws IOException on error.
648    */
649   public SharedMutex mutex(MemorySegment addr) throws IOException {
650     return mutex(addr, false);
651   }
652 
653   private SharedMutex mutex(MemorySegment addr, boolean unlockOnClose) throws IOException {
654     if (addr.isReadOnly()) {
655       throw new IOException("MemorySegment is read-only");
656     }
657     if (addr.byteSize() != 8) {
658       throw new IOException("MemorySegment must be exactly 8 bytes long");
659     }
660 
661     cleaner.checkCovered(addr);
662 
663     Futex32 futex = new Futex32(addr.asSlice(0, 4), unlockOnClose);
664     if (unlockOnClose) {
665       cleaner.registerFutex(futex);
666     }
667 
668     return futex.mutex();
669   }
670 
671   /**
672    * Returns the aligned size of this shared memory instance.
673    *
674    * @return The aligned size, in bytes.
675    */
676   public long byteSize() {
677     return size;
678   }
679 }