Skip to content

Commit 17ca830

Browse files
committed
ARTEMIS-5972: Replace JNI with Panama Foreign Function & Memory (FFM) API for Journal Native Layer
1 parent 8a8803a commit 17ca830

29 files changed

Lines changed: 5804 additions & 0 deletions

artemis-ffm/pom.xml

Lines changed: 421 additions & 0 deletions
Large diffs are not rendered by default.

artemis-ffm/src/main/java/org/apache/artemis/nativo/jlibaio/LibaioContext.java

Lines changed: 510 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.artemis.nativo.jlibaio;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
import java.io.IOException;
23+
import java.lang.foreign.MemorySegment;
24+
import java.nio.ByteBuffer;
25+
import java.util.Objects;
26+
27+
/**
28+
* This is an extension to use libaio.
29+
*/
30+
public final class LibaioFile<Callback extends SubmitInfo> implements AutoCloseable {
31+
32+
private static final Logger logger = LoggerFactory.getLogger(LibaioFile.class);
33+
34+
protected boolean open;
35+
/**
36+
* This represents a structure allocated on the native
37+
* this is a io_context_t
38+
*/
39+
final LibaioContext<Callback> ctx;
40+
41+
private int fd;
42+
43+
LibaioFile(int fd, LibaioContext ctx) {
44+
this.ctx = ctx;
45+
this.fd = fd;
46+
}
47+
48+
public int getBlockSize() throws IOException {
49+
return LibaioContext.getBlockSizeFD(fd);
50+
}
51+
52+
public boolean lock() {
53+
return LibaioContext.lock(fd);
54+
}
55+
56+
@Override
57+
public void close() throws IOException {
58+
open = false;
59+
LibaioContext.close(fd);
60+
}
61+
62+
/**
63+
* @return The size of the file.
64+
*/
65+
public long getSize() throws IOException {
66+
return LibaioContext.getSize(fd);
67+
}
68+
69+
/**
70+
* It will submit a write to the queue. The callback sent here will be received on the
71+
* {@link LibaioContext#poll(SubmitInfo[], int, int)}
72+
* In case of the libaio queue is full (e.g. returning E_AGAIN) this method will return false.
73+
* <br>
74+
* Notice: this won't hold a global reference on buffer, callback should hold a reference towards bufferWrite.
75+
* And don't free the buffer until the callback was called as this could crash the VM.
76+
*
77+
* @param position The position on the file to write. Notice this has to be a multiple of 512.
78+
* @param size The size of the buffer to use while writing.
79+
* @param buffer if you are using O_DIRECT the buffer here needs to be allocated by {@link #newBuffer(int)}.
80+
* @param callback A callback to be returned on the poll method.
81+
* @throws IOException in case of error
82+
*/
83+
public void write(long position, int size, ByteBuffer buffer, Callback callback) throws IOException {
84+
Objects.requireNonNull(callback, "Callback cannot be null");
85+
ctx.submitWrite(fd, position, size, buffer, callback);
86+
}
87+
88+
/**
89+
* It will submit a read to the queue. The callback sent here will be received on the
90+
* {@link LibaioContext#poll(SubmitInfo[], int, int)}.
91+
* In case of the libaio queue is full (e.g. returning E_AGAIN) this method will return false.
92+
* <br>
93+
* Notice: this won't hold a global reference on buffer, callback should hold a reference towards bufferWrite.
94+
* And don't free the buffer until the callback was called as this could crash the VM.
95+
* *
96+
*
97+
* @param position The position on the file to read. Notice this has to be a multiple of 512.
98+
* @param size The size of the buffer to use while reading.
99+
* @param buffer if you are using O_DIRECT the buffer here needs to be allocated by {@link #newBuffer(int)}.
100+
* @param callback A callback to be returned on the poll method.
101+
* @throws IOException in case of error
102+
* @see LibaioContext#poll(SubmitInfo[], int, int)
103+
*/
104+
public void read(long position, int size, ByteBuffer buffer, Callback callback) throws IOException {
105+
Objects.requireNonNull(callback, "Callback cannot be null");
106+
ctx.submitRead(fd, position, size, buffer, callback);
107+
}
108+
109+
/**
110+
* It will allocate a buffer to be used on libaio operations.
111+
* Buffers here are allocated with posix_memalign.
112+
* <br>
113+
* You need to explicitly free the buffer created from here using the
114+
* {@link LibaioContext#freeBuffer(MemorySegment)}.
115+
*
116+
* @param size the size of the buffer.
117+
* @return the buffer allocated.
118+
*/
119+
public MemorySegment newBuffer(int size) {
120+
return LibaioContext.newAlignedBuffer(size, 4 * 1024);
121+
}
122+
123+
/**
124+
* It will preallocate the file with a given size.
125+
*
126+
* @param size number of bytes to be filled on the file
127+
*/
128+
public void fill(int alignment, long size) throws IOException {
129+
try {
130+
LibaioContext.fill(fd, alignment, size);
131+
} catch (OutOfMemoryError e) {
132+
logger.warn("Did not have enough memory to allocate " + size + " bytes in memory while filling the file, using simple fallocate");
133+
LibaioContext.fallocate(fd, size);
134+
}
135+
}
136+
137+
/**
138+
* It will use fallocate to initialize a file.
139+
*
140+
* @param size number of bytes to be filled on the file
141+
*/
142+
public void fallocate(long size) throws IOException {
143+
LibaioContext.fallocate(fd, size);
144+
}
145+
146+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.artemis.nativo.jlibaio;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
public class NativeLogger {
23+
24+
private static final Logger logger = LoggerFactory.getLogger(NativeLogger.class);
25+
26+
public static final String PROJECT_PREFIX = "jlibaio";
27+
28+
private static final int DIFFERENT_VERSION_ID = 163001;
29+
private static final String DIFFERENT_VERSION = PROJECT_PREFIX + DIFFERENT_VERSION_ID + " You have a native library with a different version than expected";
30+
31+
public final static void incompatibleNativeLibrary() {
32+
logger.warn(DIFFERENT_VERSION);
33+
}
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.artemis.nativo.jlibaio;
19+
20+
public interface SubmitInfo {
21+
22+
void onError(int errno, String message);
23+
24+
void done();
25+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.artemis.nativo.jlibaio.ffm;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
import java.lang.foreign.MemoryLayout;
23+
import java.lang.foreign.MemorySegment;
24+
import java.lang.foreign.StructLayout;
25+
import java.lang.foreign.ValueLayout;
26+
import java.lang.invoke.VarHandle;
27+
28+
import static org.apache.artemis.nativo.jlibaio.ffm.Constants.AIO_RING_INCOMPAT_FEATURES;
29+
import static org.apache.artemis.nativo.jlibaio.ffm.Constants.AIO_RING_MAGIC;
30+
import static org.apache.artemis.nativo.jlibaio.ffm.IOEvent.IO_EVENT_LAYOUT;
31+
32+
public class AIORing {
33+
private static final Logger logger = LoggerFactory.getLogger(AIORing.class);
34+
35+
/** There is no defined aio_ring anywhere in an include,
36+
This is an implementation detail, that is a binary contract.
37+
it is safe to use the feature though. */
38+
static final StructLayout AIO_RING_LAYOUT = MemoryLayout.structLayout(
39+
// Fixed header (32 bytes)
40+
ValueLayout.JAVA_INT.withName("id"), /* kernel internal index number */
41+
ValueLayout.JAVA_INT.withName("nr"), /* number of io_events */
42+
ValueLayout.JAVA_INT.withName("head"),
43+
ValueLayout.JAVA_INT.withName("tail"),
44+
ValueLayout.JAVA_INT.withName("magic"),
45+
ValueLayout.JAVA_INT.withName("compat_features"),
46+
ValueLayout.JAVA_INT.withName("incompat_features"),
47+
ValueLayout.JAVA_INT.withName("header_length") /* size of aio_ring */
48+
).withName("aio_ring");
49+
50+
public static final long AIO_RING_HEADER_SIZE = AIO_RING_LAYOUT.byteSize();
51+
52+
public static final VarHandle AIO_RING_NR_VH =
53+
AIO_RING_LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("nr"));
54+
public static final VarHandle AIO_RING_HEAD_VH =
55+
AIO_RING_LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("head"));
56+
public static final VarHandle AIO_RING_TAIL_VH =
57+
AIO_RING_LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("tail"));
58+
public static final VarHandle AIO_RING_MAGIC_VH =
59+
AIO_RING_LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("magic"));
60+
public static final VarHandle AIO_RING_INCOMPAT_FEATURES_VH =
61+
AIO_RING_LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("incompat_features"));
62+
63+
// Check if the implementation supports AIO_RING by checking this number directly.
64+
public static boolean hasUsableRing(MemorySegment ring) {
65+
if (ring == null || ring.address() == 0L || ring.byteSize() < AIO_RING_HEADER_SIZE) {
66+
return false;
67+
}
68+
69+
MemorySegment header = ring.asSlice(0, AIO_RING_HEADER_SIZE);
70+
int magic = (int) AIO_RING_MAGIC_VH.getAcquire(header, 0L);
71+
int incompat = (int) AIO_RING_INCOMPAT_FEATURES_VH.getAcquire(header, 0L);
72+
int nr = (int) AIO_RING_NR_VH.getAcquire(header, 0L);
73+
if (logger.isTraceEnabled()) {
74+
logger.trace("nr={}, magic={}, incompat={}", nr, magic, incompat);
75+
}
76+
77+
return magic == AIO_RING_MAGIC
78+
&& incompat == AIO_RING_INCOMPAT_FEATURES
79+
&& nr > 0;
80+
}
81+
82+
// Newer versions of the kernel (newer here being a relative word, a couple years already at the time
83+
// I am writing this), will have io_context_t as an opaque type, and the real type being the aio_ring.
84+
public static MemorySegment toAioRing(MemorySegment aioCtx) {
85+
if (aioCtx == null || aioCtx.address() == 0L) {
86+
return MemorySegment.NULL;
87+
}
88+
89+
MemorySegment header = aioCtx.reinterpret(AIO_RING_HEADER_SIZE);
90+
91+
if (!hasUsableRing(header)) {
92+
return MemorySegment.NULL;
93+
}
94+
95+
int nr = (int) AIO_RING_NR_VH.getAcquire(header, 0L);
96+
long eventBytesize = IO_EVENT_LAYOUT.byteSize();
97+
long fullSize;
98+
99+
try {
100+
fullSize = Math.addExact(AIO_RING_HEADER_SIZE, Math.multiplyExact((long) nr, eventBytesize));
101+
} catch (ArithmeticException e) {
102+
logger.warn("toAioRing: overflow computing ring size (nr={}, eventBytes={})", nr, eventBytesize);
103+
return MemorySegment.NULL;
104+
}
105+
106+
if (fullSize <= AIO_RING_HEADER_SIZE) {
107+
return MemorySegment.NULL;
108+
}
109+
110+
return aioCtx.reinterpret(fullSize);
111+
}
112+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.artemis.nativo.jlibaio.ffm;
18+
19+
public final class Constants {
20+
private Constants(){}
21+
22+
static final long ONE_MEGA = 1048576L;
23+
24+
//These should be used to check if the user-space io_getevents is supported:
25+
//Linux ABI for the ring buffer: https://elixir.bootlin.com/linux/v4.20.13/source/fs/aio.c#L54
26+
//aio_read_events_ring: https://elixir.bootlin.com/linux/v4.20.13/source/fs/aio.c#L1148
27+
28+
// NOTE: if the kernel ever updates the structure, the RING-MAGIC will change and the code will switch back to normal IO calls
29+
static final int AIO_RING_MAGIC = 0xa10a10a1;
30+
static final int AIO_RING_INCOMPAT_FEATURES = 0;
31+
32+
// set this to false if you want to stop using ring reaping
33+
static final boolean RING_REAPER = true;
34+
35+
static final int PERMISSION_MODE = 0666;
36+
static final int O_RDWR = 0x0002;
37+
static final int O_CREAT = 0x0040;
38+
static final int O_DIRECT;
39+
40+
static final int LOCK_EX = 2; // Exclusive lock
41+
static final int LOCK_NB = 4; // Non-blocking lock
42+
43+
static {
44+
O_DIRECT = detectODirectFlag();
45+
}
46+
47+
/*
48+
* Detecting OS Architecture and setting O_DIRECT
49+
*
50+
* */
51+
private static int detectODirectFlag() {
52+
String arch = System.getProperty("os.arch");
53+
if ("aarch64".equals(arch) || "arm64".equals(arch) || "arm".equals(arch)) {
54+
return 0x10000;
55+
} else if ("ppc64le".equals(arch) || "ppc64".equals(arch) || "ppc".equals(arch)) {
56+
return 0x8000;
57+
}
58+
// amd64, x86_64
59+
return 0x4000;
60+
}
61+
}

0 commit comments

Comments
 (0)