Add ipc module.
This commit is contained in:
		@@ -0,0 +1,137 @@
 | 
			
		||||
package io.izzel.taboolib.module.ipc;
 | 
			
		||||
 | 
			
		||||
import io.izzel.taboolib.util.UNSAFE;
 | 
			
		||||
import sun.nio.ch.FileChannelImpl;
 | 
			
		||||
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
import java.nio.channels.FileChannel;
 | 
			
		||||
 | 
			
		||||
@SuppressWarnings("restriction")
 | 
			
		||||
public class MemoryMappedFile {
 | 
			
		||||
    private static final Method mmap;
 | 
			
		||||
    private static final Method unmmap;
 | 
			
		||||
    private static final int BYTE_ARRAY_OFFSET;
 | 
			
		||||
 | 
			
		||||
    private long addr, size;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        try {
 | 
			
		||||
            mmap = getMethod(FileChannelImpl.class, "map0", int.class, long.class, long.class);
 | 
			
		||||
            unmmap = getMethod(FileChannelImpl.class, "unmap0", long.class, long.class);
 | 
			
		||||
            BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Method getMethod(Class<?> cls, String name, Class<?>... params) throws Exception {
 | 
			
		||||
        Method m = cls.getDeclaredMethod(name, params);
 | 
			
		||||
        m.setAccessible(true);
 | 
			
		||||
        return m;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected MemoryMappedFile(FileChannel ch, long len) throws Exception {
 | 
			
		||||
        this.size = len;
 | 
			
		||||
        this.addr = (long) mmap.invoke(ch, 1, 0L, this.size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void unmap() throws Exception {
 | 
			
		||||
        unmmap.invoke(null, addr, this.size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getAddress() {
 | 
			
		||||
        return addr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte getByte(long pos) {
 | 
			
		||||
        return UNSAFE.getByte(pos + addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected byte getByteVolatile(long pos) {
 | 
			
		||||
        return UNSAFE.getByteVolatile(null, pos + addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getInt(long pos) {
 | 
			
		||||
        return UNSAFE.getInt(pos + addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected int getIntVolatile(long pos) {
 | 
			
		||||
        return UNSAFE.getIntVolatile(null, pos + addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getLong(long pos) {
 | 
			
		||||
        return UNSAFE.getLong(pos + addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected long getLongVolatile(long pos) {
 | 
			
		||||
        return UNSAFE.getLongVolatile(null, pos + addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putByte(long pos, byte val) {
 | 
			
		||||
        UNSAFE.putByte(pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void putByteVolatile(long pos, byte val) {
 | 
			
		||||
        UNSAFE.putByteVolatile(null, pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putInt(long pos, int val) {
 | 
			
		||||
        UNSAFE.putInt(pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void putIntVolatile(long pos, int val) {
 | 
			
		||||
        UNSAFE.putIntVolatile(null, pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putLong(long pos, long val) {
 | 
			
		||||
        UNSAFE.putLong(pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void putLongVolatile(long pos, long val) {
 | 
			
		||||
        UNSAFE.putLongVolatile(null, pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void getBytes(long pos, byte[] data, int offset, int length) {
 | 
			
		||||
        UNSAFE.copyMemory(null, pos + addr, data, BYTE_ARRAY_OFFSET + offset, length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setBytes(long pos, byte[] data, int offset, int length) {
 | 
			
		||||
        UNSAFE.copyMemory(data, BYTE_ARRAY_OFFSET + offset, null, pos + addr, length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected boolean compareAndSwapInt(long pos, int expected, int value) {
 | 
			
		||||
        return UNSAFE.compareAndSwapInt(null, pos + addr, expected, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected boolean compareAndSwapLong(long pos, long expected, long value) {
 | 
			
		||||
        return UNSAFE.compareAndSwapLong(null, pos + addr, expected, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected int getAndAddInt(long pos, int delta) {
 | 
			
		||||
        return UNSAFE.getAndAddInt(null, pos + addr, delta);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected long getAndAddLong(long pos, long delta) {
 | 
			
		||||
        return UNSAFE.getAndAddLong(null, pos + addr, delta);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected int addAndGetInt(long pos, int delta) {
 | 
			
		||||
        return UNSAFE.getAndAddInt(null, pos + addr, delta) + delta;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected long addAndGetLong(long pos, long delta) {
 | 
			
		||||
        return UNSAFE.getAndAddLong(null, pos + addr, delta) + delta;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected long getAndSetInt(long pos, int val) {
 | 
			
		||||
        return UNSAFE.getAndSetInt(null, pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected long getAndSetLong(long pos, long val) {
 | 
			
		||||
        return UNSAFE.getAndSetLong(null, pos + addr, val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void setMemory(long pos, long bytes, byte value) {
 | 
			
		||||
        UNSAFE.setMemory(pos + addr, bytes, value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,140 @@
 | 
			
		||||
package io.izzel.taboolib.module.ipc;
 | 
			
		||||
 | 
			
		||||
public class MessageBlock {
 | 
			
		||||
 | 
			
		||||
    private final MemoryMappedFile file;
 | 
			
		||||
    private final long baseOffset;
 | 
			
		||||
    private final long size;
 | 
			
		||||
 | 
			
		||||
    public MessageBlock(MemoryMappedFile file, long baseOffset, long size) {
 | 
			
		||||
        this.file = file;
 | 
			
		||||
        this.baseOffset = baseOffset;
 | 
			
		||||
        this.size = size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void reset() {
 | 
			
		||||
        int id = getId();
 | 
			
		||||
        file.setMemory(baseOffset, size, (byte) 0x00);
 | 
			
		||||
        putInt(0, id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getId() {
 | 
			
		||||
        return getInt(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getTimestamp() {
 | 
			
		||||
        return getLong(4);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void updateTimestamp() {
 | 
			
		||||
        putLong(4, System.currentTimeMillis());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getSize() {
 | 
			
		||||
        return size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getPayloadSize() {
 | 
			
		||||
        return size - 64;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getAddress() {
 | 
			
		||||
        return file.getAddress() + baseOffset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte getByte(long pos) {
 | 
			
		||||
        return file.getByte(mapAddress(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte getByteVolatile(long pos) {
 | 
			
		||||
        return file.getByteVolatile(mapAddress(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getInt(long pos) {
 | 
			
		||||
        return file.getInt(mapAddress(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getIntVolatile(long pos) {
 | 
			
		||||
        return file.getIntVolatile(mapAddress(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getLong(long pos) {
 | 
			
		||||
        return file.getLong(mapAddress(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getLongVolatile(long pos) {
 | 
			
		||||
        return file.getLongVolatile(mapAddress(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putByte(long pos, byte val) {
 | 
			
		||||
        file.putByte(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putByteVolatile(long pos, byte val) {
 | 
			
		||||
        file.putByteVolatile(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putInt(long pos, int val) {
 | 
			
		||||
        file.putInt(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putIntVolatile(long pos, int val) {
 | 
			
		||||
        file.putIntVolatile(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putLong(long pos, long val) {
 | 
			
		||||
        file.putLong(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putLongVolatile(long pos, long val) {
 | 
			
		||||
        file.putLongVolatile(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void getBytes(long pos, byte[] data, int offset, int length) {
 | 
			
		||||
        file.getBytes(mapAddress(pos), data, offset, length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setBytes(long pos, byte[] data, int offset, int length) {
 | 
			
		||||
        file.setBytes(mapAddress(pos), data, offset, length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean compareAndSwapInt(long pos, int expected, int value) {
 | 
			
		||||
        return file.compareAndSwapInt(mapAddress(pos), expected, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean compareAndSwapLong(long pos, long expected, long value) {
 | 
			
		||||
        return file.compareAndSwapLong(mapAddress(pos), expected, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getAndAddInt(long pos, int delta) {
 | 
			
		||||
        return file.getAndAddInt(mapAddress(pos), delta);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getAndAddLong(long pos, long delta) {
 | 
			
		||||
        return file.getAndAddLong(mapAddress(pos), delta);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int addAndGetInt(long pos, int delta) {
 | 
			
		||||
        return file.addAndGetInt(mapAddress(pos), delta);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long addAndGetLong(long pos, long delta) {
 | 
			
		||||
        return file.addAndGetLong(mapAddress(pos), delta);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getAndSetInt(long pos, int val) {
 | 
			
		||||
        return file.getAndSetInt(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getAndSetLong(long pos, long val) {
 | 
			
		||||
        return file.getAndSetLong(mapAddress(pos), val);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long mapAddress(long pos) {
 | 
			
		||||
        if (pos < size) {
 | 
			
		||||
            return baseOffset + pos;
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new IllegalArgumentException();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
package io.izzel.taboolib.module.ipc;
 | 
			
		||||
 | 
			
		||||
public interface TabooIpcClient {
 | 
			
		||||
 | 
			
		||||
    boolean connect(TabooIpcConfig config) throws Exception;
 | 
			
		||||
 | 
			
		||||
    void disconnect() throws Exception;
 | 
			
		||||
 | 
			
		||||
    boolean sendMessage(byte[] bytes, int index, int length, MessageBlock target);
 | 
			
		||||
 | 
			
		||||
    default boolean sendMessage(byte[] bytes, MessageBlock target) {
 | 
			
		||||
        return sendMessage(bytes, 0, bytes.length, target);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boolean readMessage(byte[] buf);
 | 
			
		||||
 | 
			
		||||
    int getId();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,87 @@
 | 
			
		||||
package io.izzel.taboolib.module.ipc;
 | 
			
		||||
 | 
			
		||||
import java.io.RandomAccessFile;
 | 
			
		||||
import java.nio.channels.FileChannel;
 | 
			
		||||
 | 
			
		||||
public class TabooIpcClientImpl implements TabooIpcClient {
 | 
			
		||||
 | 
			
		||||
    private volatile boolean available = false;
 | 
			
		||||
    private MemoryMappedFile file;
 | 
			
		||||
    private int id;
 | 
			
		||||
    private MessageBlock block;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean connect(TabooIpcConfig config) throws Exception {
 | 
			
		||||
        if (available) throw new IllegalStateException();
 | 
			
		||||
        long size = round(config.memorySize());
 | 
			
		||||
        long blockSize = round(config.blockSize());
 | 
			
		||||
        if (blockSize < 128 || size < (blockSize << 1) || size % blockSize != 0) return false;
 | 
			
		||||
        try (RandomAccessFile raf = new RandomAccessFile(config.fileLocation(), "rw")) {
 | 
			
		||||
            raf.setLength(size);
 | 
			
		||||
            try (FileChannel channel = raf.getChannel()) {
 | 
			
		||||
                this.file = new MemoryMappedFile(channel, size);
 | 
			
		||||
                this.id = file.getAndAddInt(0, 1);
 | 
			
		||||
                if (!login(size, blockSize, config.timeout())) {
 | 
			
		||||
                    this.file.unmap();
 | 
			
		||||
                    return available = false;
 | 
			
		||||
                }
 | 
			
		||||
                return available = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void disconnect() throws Exception {
 | 
			
		||||
        if (!available) throw new IllegalStateException();
 | 
			
		||||
        if (file != null) file.unmap();
 | 
			
		||||
        available = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean sendMessage(byte[] bytes, int index, int length, MessageBlock target) {
 | 
			
		||||
        if (!available) throw new IllegalStateException();
 | 
			
		||||
        if (bytes.length < length || target.getPayloadSize() < length)
 | 
			
		||||
            throw new IllegalArgumentException("message too long");
 | 
			
		||||
        if (target.compareAndSwapInt(16, 0, 1)) {
 | 
			
		||||
            target.setBytes(64, bytes, index, length);
 | 
			
		||||
            return target.compareAndSwapInt(16, 1, 2);
 | 
			
		||||
        } else return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean readMessage(byte[] buf) {
 | 
			
		||||
        if (!available) throw new IllegalStateException();
 | 
			
		||||
        if (this.block.getInt(16) == 2) {
 | 
			
		||||
            this.block.getBytes(64, buf, 0, (int) this.block.getPayloadSize());
 | 
			
		||||
            return this.block.compareAndSwapInt(16, 2, 0);
 | 
			
		||||
        } else return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getId() {
 | 
			
		||||
        if (!available) throw new IllegalStateException();
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean login(long size, long blockSize, long timeout) {
 | 
			
		||||
        long offset = blockSize;
 | 
			
		||||
        while (offset < size) {
 | 
			
		||||
            int prevId = file.getInt(offset);
 | 
			
		||||
            long cur = System.currentTimeMillis();
 | 
			
		||||
            long prev = file.getAndSetLong(offset + 4, cur);
 | 
			
		||||
            if (Math.abs(prev - cur) > timeout) {
 | 
			
		||||
                if (file.compareAndSwapInt(offset, prevId, id)) {
 | 
			
		||||
                    this.block = new MessageBlock(file, offset, blockSize);
 | 
			
		||||
                    this.block.reset();
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            offset += blockSize;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static long round(long i) {
 | 
			
		||||
        return (i + 0xfffL) & ~0xfffL;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
package io.izzel.taboolib.module.ipc;
 | 
			
		||||
 | 
			
		||||
public interface TabooIpcConfig {
 | 
			
		||||
 | 
			
		||||
    default long memorySize() {
 | 
			
		||||
        return 1 << 24;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default long blockSize() {
 | 
			
		||||
        return 1 << 14;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default long period() {
 | 
			
		||||
        return 10;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default long timeout() {
 | 
			
		||||
        return 60 * 1000;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String fileLocation();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
rootProject.name = 'TabooLib'
 | 
			
		||||
include 'injector'
 | 
			
		||||
include 'module-ipc'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user