/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.util.ipc.shmem;
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.util.typedef.internal.S;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_IPC_SHMEM_SPACE_DEBUG;
/**
*
*/
@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
public class IpcSharedMemorySpace implements Closeable {
/** Debug flag (enable for testing). */
private static final boolean DEBUG = Boolean.getBoolean(IGNITE_IPC_SHMEM_SPACE_DEBUG);
/** Shared memory segment size (operable). */
private final int opSize;
/** Shared memory native pointer. */
private final long shmemPtr;
/** Shared memory ID. */
private final int shmemId;
/** Semaphore set ID. */
private final int semId;
/** Local closed flag. */
private final AtomicBoolean closed = new AtomicBoolean();
/** {@code True} if space has been closed. */
private final boolean isReader;
/** Lock to protect readers and writers from concurrent close. */
private final ReadWriteLock lock = new ReentrantReadWriteLock();
/** */
private final int writerPid;
/** */
private final int readerPid;
/** */
private final String tokFileName;
/** */
private final IgniteLogger log;
/**
* This will allocate system resources for the space.
*
* @param tokFileName Token filename.
* @param writerPid Writer PID.
* @param readerPid Reader PID.
* @param size Size in bytes.
* @param reader {@code True} if reader.
* @param parent Parent logger.
* @throws IgniteCheckedException If failed.
*/
public IpcSharedMemorySpace(String tokFileName, int writerPid, int readerPid, int size, boolean reader,
IgniteLogger parent) throws IgniteCheckedException {
assert size > 0 : "Size cannot be less than 1 byte";
log = parent.getLogger(IpcSharedMemorySpace.class);
opSize = size;
shmemPtr = IpcSharedMemoryUtils.allocateSystemResources(tokFileName, size, DEBUG && log.isDebugEnabled());
shmemId = IpcSharedMemoryUtils.sharedMemoryId(shmemPtr);
semId = IpcSharedMemoryUtils.semaphoreId(shmemPtr);
isReader = reader;
this.tokFileName = tokFileName;
this.readerPid = readerPid;
this.writerPid = writerPid;
if (DEBUG && log.isDebugEnabled())
log.debug("Shared memory space has been created: " + this);
}
/**
* This should be called in order to attach to already allocated system resources.
*
* @param tokFileName Token file name (for proper cleanup).
* @param writerPid Writer PID.
* @param readerPid Reader PID.
* @param size Size.
* @param reader Reader flag.
* @param shmemId Shared memory ID.
* @param parent Logger.
* @throws IgniteCheckedException If failed.
*/
public IpcSharedMemorySpace(String tokFileName, int writerPid, int readerPid, int size, boolean reader,
int shmemId, IgniteLogger parent) throws IgniteCheckedException {
assert size > 0 : "Size cannot be less than 1 byte";
log = parent.getLogger(IpcSharedMemorySpace.class);
opSize = size;
isReader = reader;
this.shmemId = shmemId;
this.writerPid = writerPid;
this.readerPid = readerPid;
this.tokFileName = tokFileName;
shmemPtr = IpcSharedMemoryUtils.attach(shmemId, DEBUG && log.isDebugEnabled());
semId = IpcSharedMemoryUtils.semaphoreId(shmemPtr);
}
/**
* @param buf Buffer.
* @param off Offset.
* @param len Length.
* @param timeout Operation timeout in milliseconds ({@code 0} to wait forever).
* @throws IgniteCheckedException If space has been closed.
* @throws IpcSharedMemoryOperationTimedoutException If operation times out.
*/
public void write(byte[] buf, int off, int len, long timeout) throws IgniteCheckedException,
IpcSharedMemoryOperationTimedoutException {
assert buf != null;
assert len > 0;
assert buf.length >= off + len;
assert timeout >= 0;
assert !isReader;
lock.readLock().lock();
try {
if (closed.get())
throw new IgniteCheckedException("Shared memory segment has been closed: " + this);
IpcSharedMemoryUtils.writeSharedMemory(shmemPtr, buf, off, len, timeout);
}
finally {
lock.readLock().unlock();
}
}
/**
* @param buf Buffer.
* @param off Offset.
* @param len Length.
* @param timeout Operation timeout in milliseconds ({@code 0} to wait forever).
* @throws IgniteCheckedException If space has been closed.
* @throws IpcSharedMemoryOperationTimedoutException If operation times out.
*/
public void write(ByteBuffer buf, int off, int len, long timeout) throws IgniteCheckedException,
IpcSharedMemoryOperationTimedoutException {
assert buf != null;
assert len > 0;
assert buf.limit() >= off + len;
assert timeout >= 0;
assert !isReader;
lock.readLock().lock();
try {
if (closed.get())
throw new IgniteCheckedException("Shared memory segment has been closed: " + this);
IpcSharedMemoryUtils.writeSharedMemoryByteBuffer(shmemPtr, buf, off, len, timeout);
}
finally {
lock.readLock().unlock();
}
}
/**
* Blocks until at least 1 byte is read.
*
* @param buf Buffer.
* @param off Offset.
* @param len Length.
* @param timeout Operation timeout in milliseconds ({@code 0} to wait forever).
* @return Read bytes count.
* @throws IgniteCheckedException If space has been closed.
* @throws IpcSharedMemoryOperationTimedoutException If operation times out.
*/
public int read(byte[] buf, int off, int len, long timeout) throws IgniteCheckedException,
IpcSharedMemoryOperationTimedoutException {
assert buf != null;
assert len > 0;
assert buf.length >= off + len;
assert isReader;
lock.readLock().lock();
try {
if (closed.get())
throw new IgniteCheckedException("Shared memory segment has been closed: " + this);
return (int) IpcSharedMemoryUtils.readSharedMemory(shmemPtr, buf, off, len, timeout);
}
finally {
lock.readLock().unlock();
}
}
/**
* Blocks until at least 1 byte is read.
*
* @param buf Buffer.
* @param off Offset.
* @param len Length.
* @param timeout Operation timeout in milliseconds ({@code 0} to wait forever).
* @return Read bytes count.
* @throws IgniteCheckedException If space has been closed.
* @throws IpcSharedMemoryOperationTimedoutException If operation times out.
*/
public int read(ByteBuffer buf, int off, int len, long timeout) throws IgniteCheckedException,
IpcSharedMemoryOperationTimedoutException {
assert buf != null;
assert len > 0;
assert buf.capacity() >= off + len;
assert isReader;
lock.readLock().lock();
try {
if (closed.get())
throw new IgniteCheckedException("Shared memory segment has been closed: " + this);
return (int) IpcSharedMemoryUtils.readSharedMemoryByteBuffer(shmemPtr, buf, off, len, timeout);
}
finally {
lock.readLock().unlock();
}
}
/** {@inheritDoc} */
@Override public void close() {
close0(false);
}
/**
* Forcibly closes the space and frees all system resources.
* <p>
* This method should be called with caution as it may result to the other-party
* process crash. It is intended to call when there was an IO error during handshake
* and other party has not yet attached to the space.
*/
public void forceClose() {
close0(true);
}
/**
* @return Shared memory ID.
*/
public int sharedMemoryId() {
return shmemId;
}
/**
* @return Semaphore set ID.
*/
public int semaphoreId() {
return semId;
}
/**
* @param force {@code True} to close the space.
*/
private void close0(boolean force) {
if (!closed.compareAndSet(false, true))
return;
IpcSharedMemoryUtils.ipcClose(shmemPtr);
// Wait all readers and writes to leave critical section.
lock.writeLock().lock();
try {
IpcSharedMemoryUtils.freeSystemResources(tokFileName, shmemPtr, force);
}
finally {
lock.writeLock().unlock();
}
if (DEBUG && log.isDebugEnabled())
log.debug("Shared memory space has been closed: " + this);
}
/**
* @return Bytes available for read.
* @throws IgniteCheckedException If failed.
*/
public int unreadCount() throws IgniteCheckedException {
lock.readLock().lock();
try {
if (closed.get())
throw new IgniteCheckedException("Shared memory segment has been closed: " + this);
return IpcSharedMemoryUtils.unreadCount(shmemPtr);
}
finally {
lock.readLock().unlock();
}
}
/**
* @return Shared memory pointer.
*/
public long sharedMemPointer() {
return shmemPtr;
}
/**
* @return Reader PID.
*/
public int readerPid() {
return readerPid;
}
/**
* @return Writer PID.
*/
public int writerPid() {
return writerPid;
}
/**
* @return Vis-a-vis PID.
*/
public int otherPartyPid() {
return isReader ? writerPid : readerPid;
}
/**
* @return Token file name used to create shared memory space.
*/
public String tokenFileName() {
return tokFileName;
}
/**
* @return Space size.
*/
public int size() {
return opSize;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(IpcSharedMemorySpace.class, this, "closed", closed.get());
}
}