From 1d5c1f91efe346544f5f69a8fa8eda51e15146c0 Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Wed, 4 Dec 2019 22:54:02 +0800 Subject: [PATCH] Add ipc module. --- .../taboolib/module/ipc/MemoryMappedFile.java | 137 +++++++++++++++++ .../taboolib/module/ipc/MessageBlock.java | 140 ++++++++++++++++++ .../taboolib/module/ipc/TabooIpcClient.java | 19 +++ .../module/ipc/TabooIpcClientImpl.java | 87 +++++++++++ .../taboolib/module/ipc/TabooIpcConfig.java | 22 +++ .../java}/io/izzel/taboolib/util/UNSAFE.java | 0 settings.gradle | 1 + 7 files changed, 406 insertions(+) create mode 100644 module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MemoryMappedFile.java create mode 100644 module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MessageBlock.java create mode 100644 module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClient.java create mode 100644 module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClientImpl.java create mode 100644 module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcConfig.java rename {src/main/scala => module-ipc/src/main/java}/io/izzel/taboolib/util/UNSAFE.java (100%) diff --git a/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MemoryMappedFile.java b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MemoryMappedFile.java new file mode 100644 index 0000000..77eed59 --- /dev/null +++ b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MemoryMappedFile.java @@ -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); + } +} \ No newline at end of file diff --git a/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MessageBlock.java b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MessageBlock.java new file mode 100644 index 0000000..5cfdc15 --- /dev/null +++ b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/MessageBlock.java @@ -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(); + } + } +} diff --git a/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClient.java b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClient.java new file mode 100644 index 0000000..7a4231e --- /dev/null +++ b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClient.java @@ -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(); + +} diff --git a/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClientImpl.java b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClientImpl.java new file mode 100644 index 0000000..f984d8e --- /dev/null +++ b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcClientImpl.java @@ -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; + } +} diff --git a/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcConfig.java b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcConfig.java new file mode 100644 index 0000000..fa46d6a --- /dev/null +++ b/module-ipc/src/main/java/io/izzel/taboolib/module/ipc/TabooIpcConfig.java @@ -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(); +} diff --git a/src/main/scala/io/izzel/taboolib/util/UNSAFE.java b/module-ipc/src/main/java/io/izzel/taboolib/util/UNSAFE.java similarity index 100% rename from src/main/scala/io/izzel/taboolib/util/UNSAFE.java rename to module-ipc/src/main/java/io/izzel/taboolib/util/UNSAFE.java diff --git a/settings.gradle b/settings.gradle index cfa1e85..761d9a5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ rootProject.name = 'TabooLib' include 'injector' +include 'module-ipc'