Skip to content

Commit

Permalink
Split common Shm logic to base class
Browse files Browse the repository at this point in the history
And decouple the close and unlink functions.

There was a lot of duplicate code between ShmLinux, ShmMacOS,
and ShmWindows, which is now in the internal ShmBase class.

This made it easier to have consistent logic around the close
vs. unlink behavior across platforms, with less copy/pasted code.
  • Loading branch information
ctrueden committed Jul 20, 2024
1 parent 7f2f57b commit 5aa52d4
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 286 deletions.
2 changes: 1 addition & 1 deletion src/main/java/org/apposed/appose/NDArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import java.util.Arrays;

/**
* Represents a multi-dimensional array similar to NumPy ndarray.
* Represents a multidimensional array similar to a NumPy ndarray.
* <p>
* The array contains elements of a {@link DType data type}, arranged in a
* particular {@link Shape}, and flattened into {@link SharedMemory},
Expand Down
22 changes: 16 additions & 6 deletions src/main/java/org/apposed/appose/SharedMemory.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ static SharedMemory createOrAttach(String name, boolean create, int size) {
*
* @return The length in bytes of the shared memory.
*/
long size();
int size();

/**
* JNA pointer to the shared memory segment.
Expand All @@ -115,19 +115,29 @@ static SharedMemory createOrAttach(String name, boolean create, int size) {
*/
Pointer pointer();

/**
* Sets whether the {@link #unlink()} method should be invoked to destroy
* the shared memory block when the {@link #close()} method is called.
* <p>
* By default, shared memory objects constructed with {@link #create} will
* behave this way, whereas shared memory objects constructed with
* {@link #attach} will not. But this method allows to override the behavior.
* </p>
*/
void unlinkOnClose(boolean unlinkOnClose);

/**
* Requests that the underlying shared memory block be destroyed.
* In order to ensure proper cleanup of resources, unlink should be
* called once (and only once) across all processes which have access
* to the shared memory block.
*/
default void unlink() {
throw new UnsupportedOperationException();
}
void unlink();

/**
* Closes access to the shared memory from this instance but does
* not destroy the shared memory block.
* Closes access to the shared memory from this instance,
* but does not necessarily destroy the shared memory block.
* See also {@link #unlinkOnClose(boolean)}.
*/
@Override
void close();
Expand Down
13 changes: 12 additions & 1 deletion src/main/java/org/apposed/appose/ShmFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@
*/
package org.apposed.appose;

/**
* Interface for platform-specific creation of {@link SharedMemory} instances.
* <p>
* Each platform (e.g. Linux, macOS, Windows) provides its own implementation
* of this interface, which knows how to manufacture {@link SharedMemory} blocks
* for that platform. These implementations are declared as implementations in
* {@code META-INF/services/org.apposed.appose.ShmFactory}, so that Java's
* {@code ServiceLoader} can discover them in an extensible way, and then use
* the one best suited for the platform at hand.
* </p>
*/
public interface ShmFactory {
SharedMemory create(String name, boolean create, int size);
SharedMemory create(String name, boolean create, int size);
}
130 changes: 130 additions & 0 deletions src/main/java/org/apposed/appose/shm/ShmBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*-
* #%L
* Appose: multi-language interprocess cooperation with shared memory.
* %%
* Copyright (C) 2023 - 2024 Appose developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/

package org.apposed.appose.shm;

import com.sun.jna.Pointer;
import org.apposed.appose.SharedMemory;

/**
* Base class for platform-specific shared memory implementations.
*
* @author Carlos Garcia Lopez de Haro
* @author Tobias Pietzsch
* @author Curtis Rueden
*/
abstract class ShmBase<HANDLE> implements SharedMemory {

/** Struct containing shm details, including name, size, pointer(s), and handle. */
protected final ShmInfo<HANDLE> info;

/** Whether the memory block has been closed. */
private boolean closed;

/** Whether the memory block has been unlinked. */
private boolean unlinked;

protected ShmBase(final ShmInfo<HANDLE> info) {
this.info = info;
}

protected abstract void doClose();
protected abstract void doUnlink();

@Override
public String name() {
return info.name;
}

@Override
public int size() {
return info.size;
}

@Override
public Pointer pointer() {
return info.pointer;
}

@Override
public void unlinkOnClose(boolean unlinkOnClose) {
info.unlinkOnClose = unlinkOnClose;
}

@Override
public synchronized void unlink() {
if (unlinked) throw new IllegalStateException("Shared memory '" + info.name + "' is already unlinked.");
doClose();
doUnlink();
unlinked = true;
}

@Override
public synchronized void close() {
if (closed) return;
doClose();
if (info.unlinkOnClose) doUnlink();
closed = true;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName()).append("{");
sb.append("name='").append(name()).append("', size=").append(size());
if (info.pointer != null) sb.append(", pointer=").append(info.pointer);
if (info.writePointer != null) sb.append(", writePointer=").append(info.writePointer);
if (info.handle != null) sb.append("handle=").append(info.handle);
sb.append(", closed=").append(closed);
sb.append(", unlinked=").append(unlinked);
sb.append("}");
return sb.toString();
}

/** Struct containing details about this shared memory block. */
protected static class ShmInfo<HANDLE> {

/** Unique name that identifies the shared memory segment. */
String name;

/** Size in bytes. */
int size;

/** Pointer referencing the shared memory. */
Pointer pointer;

Pointer writePointer;

/** File handle for the shared memory block's (pseudo-)file. */
HANDLE handle;

/** Whether to destroy the shared memory block when {@link #close()} is called. */
boolean unlinkOnClose;
}
}
111 changes: 29 additions & 82 deletions src/main/java/org/apposed/appose/shm/ShmLinux.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,10 @@

/**
* Linux-specific shared memory implementation.
* <p>
* TODO separate unlink and close
* </p>
*
* @author Carlos Garcia Lopez de Haro
* @author Tobias Pietzsch
* @author Curtis Rueden
*/
public class ShmLinux implements ShmFactory {

Expand All @@ -62,35 +60,32 @@ public SharedMemory create(final String name, final boolean create, final int si
return new SharedMemoryLinux(name, create, size);
}

private static class SharedMemoryLinux implements SharedMemory {
private static class SharedMemoryLinux extends ShmBase<Integer> {

/**
* File descriptor
*/
private final int fd;

/**
* Size in bytes
*/
private final int size;
// name without leading slash
private SharedMemoryLinux(final String name, final boolean create, final int size) {
super(prepareShm(name, create, size));
}

/**
* Pointer referencing the shared memory
*/
private final Pointer pointer;
@Override
protected void doUnlink() {
LibRtOrC.shm_unlink(name());
}

/**
* Unique name that identifies the shared memory segment.
*/
private final String name;
@Override
protected void doClose() {
// Unmap the shared memory
if (pointer() != Pointer.NULL && LibRtOrC.munmap(pointer(), size()) == -1) {
throw new RuntimeException("munmap failed. Errno: " + Native.getLastError());
}

/**
* Whether the memory block has been closed and unlinked
*/
private boolean unlinked = false;
// Close the file descriptor
if (LibRtOrC.close(info.handle) == -1) {
throw new RuntimeException("close failed. Errno: " + Native.getLastError());
}
}

// name without leading slash
private SharedMemoryLinux(final String name, final boolean create, final int size) {
private static ShmInfo<Integer> prepareShm(String name, boolean create, int size) {
String shm_name;
long prevSize;
if (name == null) {
Expand Down Expand Up @@ -123,49 +118,13 @@ private SharedMemoryLinux(final String name, final boolean create, final int siz
throw new RuntimeException("mmap failed, errno: " + Native.getLastError());
}

this.size = shm_size;
this.name = withoutLeadingSlash(shm_name);
this.fd = shmFd;
this.pointer = pointer;
}

@Override
public String name() {
return name;
}

@Override
public Pointer pointer() {
return pointer;
}

@Override
public long size() {
return size;
}

/**
* Unmap and close the shared memory. Necessary to eliminate the shared memory block
*/
@Override
public synchronized void close() {
if (unlinked) {
return;
}

// Unmap the shared memory
if (this.pointer != Pointer.NULL && LibRtOrC.munmap(this.pointer, size) == -1) {
throw new RuntimeException("munmap failed. Errno: " + Native.getLastError());
}

// Close the file descriptor
if (LibRtOrC.close(this.fd) == -1) {
throw new RuntimeException("close failed. Errno: " + Native.getLastError());
}

// Unlink the shared memory object
LibRtOrC.shm_unlink(this.name);
unlinked = true;
ShmInfo<Integer> info = new ShmInfo<>();
info.size = shm_size;
info.name = withoutLeadingSlash(shm_name);
info.pointer = pointer;
info.handle = shmFd;
info.unlinkOnClose = create;
return info;
}

/**
Expand Down Expand Up @@ -202,18 +161,6 @@ private static long getSHMSize(final int shmFd) {
return size;
}

@Override
public String toString() {
return "ShmLinux{" +
"fd=" + fd +
", size=" + size +
", pointer=" + pointer +
", name='" + name + '\'' +
", unlinked=" + unlinked +
'}';
}


private static class LibRtOrC {

/**
Expand Down
Loading

0 comments on commit 5aa52d4

Please sign in to comment.