fileDescriptor = control.openFile(file, true);
446 |
447 | ByteBuffer bufferWrite = LibaioContext.newAlignedBuffer(4096, 4096);
448 |
449 | try {
450 | for (int i = 0; i < 4096; i++) {
451 | bufferWrite.put((byte) 'B');
452 | }
453 |
454 | for (int j = 0; j < LIBAIO_QUEUE_SIZE * 2; j++) {
455 | for (int i = 0; i < LIBAIO_QUEUE_SIZE; i++) {
456 | TestInfo countClass = new TestInfo();
457 | fileDescriptor.write(i * 4096, 4096, bufferWrite, countClass);
458 | }
459 |
460 | Assert.assertEquals(LIBAIO_QUEUE_SIZE, control.poll(callbacks, LIBAIO_QUEUE_SIZE, LIBAIO_QUEUE_SIZE));
461 |
462 | for (int i = 0; i < LIBAIO_QUEUE_SIZE; i++) {
463 | Assert.assertNotNull(callbacks[i]);
464 | callbacks[i] = null;
465 | }
466 | }
467 |
468 | TestInfo.checkLeaks();
469 | } finally {
470 | LibaioContext.freeBuffer(bufferWrite);
471 | }
472 | }
473 |
474 | @Test
475 | public void testLock() throws Exception {
476 | File file = temporaryFolder.newFile("test.bin");
477 |
478 | LibaioFile fileDescriptor = control.openFile(file, true);
479 | fileDescriptor.lock();
480 |
481 | fileDescriptor.close();
482 | }
483 |
484 | @Test
485 | public void testAlloc() throws Exception {
486 | File file = temporaryFolder.newFile("test.bin");
487 |
488 | LibaioFile fileDescriptor = control.openFile(file, true);
489 | fileDescriptor.fill(fileDescriptor.getBlockSize(),10 * 1024 * 1024);
490 |
491 | fileDescriptor.close();
492 | }
493 |
494 | @Test
495 | public void testReleaseNullBuffer() throws Exception {
496 | boolean failed = false;
497 | try {
498 | LibaioContext.freeBuffer(null);
499 | } catch (Exception expected) {
500 | failed = true;
501 | }
502 |
503 | Assert.assertTrue("Exception happened!", failed);
504 |
505 | }
506 |
507 | @Test
508 | public void testMemset() throws Exception {
509 |
510 | ByteBuffer buffer = LibaioContext.newAlignedBuffer(4096 * 8, 4096);
511 |
512 | for (int i = 0; i < buffer.capacity(); i++) {
513 | buffer.put((byte) 'z');
514 | }
515 |
516 | buffer.position(0);
517 |
518 | for (int i = 0; i < buffer.capacity(); i++) {
519 | Assert.assertEquals((byte) 'z', buffer.get());
520 | }
521 |
522 | control.memsetBuffer(buffer);
523 |
524 | buffer.position(0);
525 |
526 | for (int i = 0; i < buffer.capacity(); i++) {
527 | Assert.assertEquals((byte) 0, buffer.get());
528 | }
529 |
530 | LibaioContext.freeBuffer(buffer);
531 |
532 | }
533 |
534 | @Test
535 | public void testIOExceptionConditions() throws Exception {
536 | boolean exceptionThrown = false;
537 |
538 | control.close();
539 | control = new LibaioContext<>(LIBAIO_QUEUE_SIZE, false, true);
540 | try {
541 | // There is no space for a queue this huge, the native layer should throw the exception
542 | LibaioContext newController = new LibaioContext(Integer.MAX_VALUE, false, true);
543 | } catch (RuntimeException e) {
544 | exceptionThrown = true;
545 | }
546 |
547 | Assert.assertTrue(exceptionThrown);
548 | exceptionThrown = false;
549 |
550 | try {
551 | // this should throw an exception, we shouldn't be able to open a directory!
552 | control.openFile(temporaryFolder.getRoot(), true);
553 | } catch (IOException expected) {
554 | exceptionThrown = true;
555 | }
556 |
557 | Assert.assertTrue(exceptionThrown);
558 |
559 | exceptionThrown = false;
560 |
561 | LibaioFile fileDescriptor = control.openFile(temporaryFolder.newFile(), true);
562 | fileDescriptor.close();
563 | try {
564 | fileDescriptor.close();
565 | } catch (IOException expected) {
566 | exceptionThrown = true;
567 | }
568 |
569 | Assert.assertTrue(exceptionThrown);
570 |
571 | fileDescriptor = control.openFile(temporaryFolder.newFile(), true);
572 |
573 | ByteBuffer buffer = fileDescriptor.newBuffer(4096);
574 |
575 | try {
576 | for (int i = 0; i < 4096; i++) {
577 | buffer.put((byte) 'a');
578 | }
579 |
580 | for (int i = 0; i < LIBAIO_QUEUE_SIZE; i++) {
581 | fileDescriptor.write(i * 4096, 4096, buffer, new TestInfo());
582 | }
583 |
584 | boolean ex = false;
585 | try {
586 | fileDescriptor.write(0, 4096, buffer, new TestInfo());
587 | } catch (Exception e) {
588 | ex = true;
589 | }
590 |
591 | Assert.assertTrue(ex);
592 |
593 | TestInfo[] callbacks = new TestInfo[LIBAIO_QUEUE_SIZE];
594 | Assert.assertEquals(LIBAIO_QUEUE_SIZE, control.poll(callbacks, LIBAIO_QUEUE_SIZE, LIBAIO_QUEUE_SIZE));
595 |
596 | // it should be possible to write now after queue space being released
597 | fileDescriptor.write(0, 4096, buffer, new TestInfo());
598 | Assert.assertEquals(1, control.poll(callbacks, 1, 100));
599 |
600 | TestInfo errorCallback = new TestInfo();
601 | // odd positions will have failures through O_DIRECT
602 | fileDescriptor.read(3, 4096, buffer, errorCallback);
603 | Assert.assertEquals(1, control.poll(callbacks, 1, 50));
604 | Assert.assertTrue(callbacks[0].isError());
605 | Assert.assertSame(errorCallback, (callbacks[0]));
606 |
607 | // to help GC and the checkLeaks
608 | callbacks = null;
609 | errorCallback = null;
610 |
611 | TestInfo.checkLeaks();
612 |
613 | exceptionThrown = false;
614 | try {
615 | LibaioContext.newAlignedBuffer(300, 4096);
616 | } catch (RuntimeException e) {
617 | exceptionThrown = true;
618 | }
619 |
620 | Assert.assertTrue(exceptionThrown);
621 |
622 | exceptionThrown = false;
623 | try {
624 | LibaioContext.newAlignedBuffer(-4096, 4096);
625 | } catch (RuntimeException e) {
626 | exceptionThrown = true;
627 | }
628 |
629 | Assert.assertTrue(exceptionThrown);
630 | } finally {
631 | LibaioContext.freeBuffer(buffer);
632 | }
633 | }
634 |
635 | @Test
636 | public void testBlockedCallback() throws Exception {
637 | final LibaioContext blockedContext = new LibaioContext(LIBAIO_QUEUE_SIZE, true, true);
638 | Thread t = new Thread() {
639 | @Override
640 | public void run() {
641 | blockedContext.poll();
642 | }
643 | };
644 |
645 | t.start();
646 |
647 | int NUMBER_OF_BLOCKS = LIBAIO_QUEUE_SIZE * 10;
648 |
649 | final CountDownLatch latch = new CountDownLatch(NUMBER_OF_BLOCKS);
650 |
651 | File file = temporaryFolder.newFile("sub-file.txt");
652 | LibaioFile aioFile = blockedContext.openFile(file, true);
653 | aioFile.fill(aioFile.getBlockSize(),NUMBER_OF_BLOCKS * 4096);
654 |
655 | final AtomicInteger errors = new AtomicInteger(0);
656 |
657 | class MyCallback implements SubmitInfo {
658 |
659 | @Override
660 | public void onError(int errno, String message) {
661 | errors.incrementAndGet();
662 | }
663 |
664 | @Override
665 | public void done() {
666 | latch.countDown();
667 | }
668 | }
669 |
670 | MyCallback callback = new MyCallback();
671 |
672 | ByteBuffer buffer = LibaioContext.newAlignedBuffer(4096, 4096);
673 |
674 | for (int i = 0; i < 4096; i++) {
675 | buffer.put((byte) 'a');
676 | }
677 |
678 | long start = System.currentTimeMillis();
679 |
680 | for (int i = 0; i < NUMBER_OF_BLOCKS; i++) {
681 | aioFile.write(i * 4096, 4096, buffer, callback);
682 | }
683 |
684 | long end = System.currentTimeMillis();
685 |
686 | latch.await();
687 |
688 | logger.debug("time = " + (end - start) + " writes/second=" + NUMBER_OF_BLOCKS * 1000L / (end - start));
689 |
690 | blockedContext.close();
691 | t.join();
692 | }
693 |
694 | private void fillupFile(File file, int blocks) throws IOException {
695 | FileOutputStream fileOutputStream = new FileOutputStream(file);
696 | byte[] bufferWrite = new byte[4096];
697 | for (int i = 0; i < 4096; i++) {
698 | bufferWrite[i] = (byte) 0;
699 | }
700 |
701 | for (int i = 0; i < blocks; i++) {
702 | fileOutputStream.write(bufferWrite);
703 | }
704 |
705 | fileOutputStream.close();
706 | }
707 |
708 | static class TestInfo implements SubmitInfo {
709 |
710 | static final Cleaner cleaner;
711 |
712 | static {
713 | Cleaner tempCleaner;
714 | try {
715 | tempCleaner = Cleaner.create();
716 | } catch (Throwable e) {
717 | e.printStackTrace();
718 | tempCleaner = null;
719 | }
720 | cleaner = tempCleaner;
721 | }
722 | static AtomicInteger count = new AtomicInteger();
723 |
724 | public static void checkLeaks() throws InterruptedException {
725 | for (int i = 0; count.get() != 0 && i < 50; i++) {
726 | WeakReference reference = new WeakReference(new Object());
727 | while (reference.get() != null) {
728 | System.gc();
729 | Thread.sleep(100);
730 | }
731 | }
732 | Assert.assertEquals(0, count.get());
733 | }
734 |
735 | boolean error = false;
736 | String errorMessage;
737 | int errno;
738 |
739 | TestInfo() {
740 | count.incrementAndGet();
741 | cleaner.register(this, count::decrementAndGet);
742 |
743 | }
744 |
745 | @Override
746 | public void onError(int errno, String message) {
747 | this.errno = errno;
748 | this.errorMessage = message;
749 | this.error = true;
750 | }
751 |
752 | @Override
753 | public void done() {
754 | }
755 |
756 | public int getErrno() {
757 | return errno;
758 | }
759 |
760 | public void setErrno(int errno) {
761 | this.errno = errno;
762 | }
763 |
764 | public boolean isError() {
765 | return error;
766 | }
767 |
768 | public void setError(boolean error) {
769 | this.error = error;
770 | }
771 |
772 | public String getErrorMessage() {
773 | return errorMessage;
774 | }
775 |
776 | public void setErrorMessage(String errorMessage) {
777 | this.errorMessage = errorMessage;
778 | }
779 | }
780 | }
781 |
--------------------------------------------------------------------------------
/src/test/java/org/apache/activemq/artemis/nativo/jlibaio/test/LoadedTest.java:
--------------------------------------------------------------------------------
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.activemq.artemis.nativo.jlibaio.test;
19 |
20 | import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
21 | import org.junit.Assert;
22 | import org.junit.Assume;
23 | import org.junit.Test;
24 |
25 | public class LoadedTest {
26 | private static final String OS = System.getProperty("os.name").toLowerCase();
27 | private static final boolean IS_LINUX = OS.startsWith("linux");
28 |
29 | @Test
30 | public void testValidateIsLoaded() {
31 | Assume.assumeTrue(IS_LINUX);
32 | Assert.assertTrue(LibaioContext.isLoaded());
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/org/apache/activemq/artemis/nativo/jlibaio/test/OpenCloseContextTest.java:
--------------------------------------------------------------------------------
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.activemq.artemis.nativo.jlibaio.test;
19 |
20 | import java.io.File;
21 | import java.nio.ByteBuffer;
22 | import java.util.concurrent.CountDownLatch;
23 | import java.util.concurrent.TimeUnit;
24 |
25 | import org.apache.activemq.artemis.nativo.jlibaio.LibaioContext;
26 | import org.apache.activemq.artemis.nativo.jlibaio.LibaioFile;
27 | import org.apache.activemq.artemis.nativo.jlibaio.SubmitInfo;
28 | import org.junit.Assume;
29 | import org.junit.BeforeClass;
30 | import org.junit.Rule;
31 | import org.junit.Test;
32 | import org.junit.rules.TemporaryFolder;
33 | import org.slf4j.Logger;
34 | import org.slf4j.LoggerFactory;
35 |
36 | public class OpenCloseContextTest {
37 |
38 | Logger logger = LoggerFactory.getLogger(OpenCloseContextTest.class);
39 |
40 | @BeforeClass
41 | public static void testAIO() {
42 | Assume.assumeTrue(LibaioContext.isLoaded());
43 | }
44 |
45 | @Rule
46 | public TemporaryFolder folder;
47 |
48 | public OpenCloseContextTest() {
49 | folder = new TemporaryFolder(new File("./target"));
50 | }
51 |
52 | @Test
53 | public void testRepeatOpenCloseContext() throws Exception {
54 | ByteBuffer buffer = LibaioContext.newAlignedBuffer(512, 512);
55 | for (int i = 0; i < 512; i++)
56 | buffer.put((byte) 'x');
57 |
58 | for (int i = 0; i < 10; i++) {
59 | logger.debug("#test " + i);
60 | final LibaioContext control = new LibaioContext<>(5, true, true);
61 | Thread t = new Thread() {
62 | @Override
63 | public void run() {
64 | control.poll();
65 | }
66 | };
67 | t.start();
68 | LibaioFile file = control.openFile(folder.newFile(), true);
69 | file.fill(file.getBlockSize(),4 * 1024);
70 | final CountDownLatch insideMethod = new CountDownLatch(1);
71 | final CountDownLatch awaitInside = new CountDownLatch(1);
72 | file.write(0, 512, buffer, new SubmitInfo() {
73 | @Override
74 | public void onError(int errno, String message) {
75 |
76 | }
77 |
78 | @Override
79 | public void done() {
80 | insideMethod.countDown();
81 | try {
82 | awaitInside.await();
83 | } catch (Throwable e) {
84 | e.printStackTrace();
85 | }
86 | logger.debug("done");
87 | }
88 | });
89 |
90 | insideMethod.await();
91 |
92 | file.write(512, 512, buffer, new SubmitInfo() {
93 | @Override
94 | public void onError(int errno, String message) {
95 | }
96 |
97 | @Override
98 | public void done() {
99 | }
100 | });
101 |
102 | awaitInside.countDown();
103 | control.close();
104 |
105 | t.join();
106 | }
107 |
108 | }
109 |
110 | @Test
111 | public void testRepeatOpenCloseContext2() throws Exception {
112 | ByteBuffer buffer = LibaioContext.newAlignedBuffer(512, 512);
113 | for (int i = 0; i < 512; i++)
114 | buffer.put((byte) 'x');
115 |
116 | for (int i = 0; i < 10; i++) {
117 | logger.debug("#test " + i);
118 | final LibaioContext control = new LibaioContext<>(5, true, true);
119 | Thread t = new Thread() {
120 | @Override
121 | public void run() {
122 | control.poll();
123 | }
124 | };
125 | t.start();
126 | LibaioFile file = control.openFile(folder.newFile(), true);
127 | file.fill(file.getBlockSize(), 4 * 1024);
128 | final CountDownLatch insideMethod = new CountDownLatch(1);
129 | final CountDownLatch awaitInside = new CountDownLatch(1);
130 | file.write(0, 512, buffer, new SubmitInfo() {
131 | @Override
132 | public void onError(int errno, String message) {
133 |
134 | }
135 |
136 | @Override
137 | public void done() {
138 | insideMethod.countDown();
139 | try {
140 | awaitInside.await(100, TimeUnit.MILLISECONDS);
141 | } catch (Throwable e) {
142 | e.printStackTrace();
143 | }
144 | logger.debug("done");
145 | }
146 | });
147 |
148 | insideMethod.await();
149 |
150 | file.write(512, 512, buffer, new SubmitInfo() {
151 | @Override
152 | public void onError(int errno, String message) {
153 | }
154 |
155 | @Override
156 | public void done() {
157 | }
158 | });
159 |
160 | awaitInside.countDown();
161 |
162 | control.close();
163 |
164 | t.join();
165 | }
166 |
167 | }
168 |
169 | @Test
170 | public void testCloseAndStart() throws Exception {
171 | final LibaioContext control2 = new LibaioContext<>(5, true, true);
172 |
173 | final LibaioContext control = new LibaioContext<>(5, true, true);
174 | control.close();
175 | control.poll();
176 |
177 | control2.close();
178 | control2.poll();
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/src/test/java/org/apache/activemq/artemis/nativo/jlibaio/test/ReusableLatch.java:
--------------------------------------------------------------------------------
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.activemq.artemis.nativo.jlibaio.test;
18 |
19 | import java.util.concurrent.TimeUnit;
20 | import java.util.concurrent.locks.AbstractQueuedSynchronizer;
21 |
22 | /**
23 | * This class will use the framework provided to by AbstractQueuedSynchronizer.
24 | * AbstractQueuedSynchronizer is the framework for any sort of concurrent synchronization, such as Semaphores, events, etc, based on AtomicIntegers.
25 | *
26 | * This class works just like CountDownLatch, with the difference you can also increase the counter
27 | *
28 | * It could be used for sync points when one process is feeding the latch while another will wait when everything is done. (e.g. waiting IO completions to finish)
29 | *
30 | * On ActiveMQ Artemis we have the requirement of increment and decrement a counter until the user fires a ready event (commit). At that point we just act as a regular countDown.
31 | *
32 | * Note: This latch is reusable. Once it reaches zero, you can call up again, and reuse it on further waits.
33 | *
34 | * For example: prepareTransaction will wait for the current completions, and further adds will be called on the latch. Later on when commit is called you can reuse the same latch.
35 | */
36 | public class ReusableLatch {
37 |
38 | /**
39 | * Look at the doc and examples provided by AbstractQueuedSynchronizer for more information
40 | *
41 | * @see AbstractQueuedSynchronizer
42 | */
43 | @SuppressWarnings("serial")
44 | private static class CountSync extends AbstractQueuedSynchronizer {
45 |
46 | private CountSync(int count) {
47 | setState(count);
48 | }
49 |
50 | public int getCount() {
51 | return getState();
52 | }
53 |
54 | public void setCount(final int count) {
55 | setState(count);
56 | }
57 |
58 | @Override
59 | public int tryAcquireShared(final int numberOfAqcquires) {
60 | return getState() == 0 ? 1 : -1;
61 | }
62 |
63 | public void add() {
64 | for (;;) {
65 | int actualState = getState();
66 | int newState = actualState + 1;
67 | if (compareAndSetState(actualState, newState)) {
68 | return;
69 | }
70 | }
71 | }
72 |
73 | @Override
74 | public boolean tryReleaseShared(final int numberOfReleases) {
75 | for (;;) {
76 | int actualState = getState();
77 | if (actualState == 0) {
78 | return true;
79 | }
80 |
81 | int newState = actualState - numberOfReleases;
82 |
83 | if (newState < 0) {
84 | newState = 0;
85 | }
86 |
87 | if (compareAndSetState(actualState, newState)) {
88 | return newState == 0;
89 | }
90 | }
91 | }
92 | }
93 |
94 | private final CountSync control;
95 |
96 | public ReusableLatch() {
97 | this(0);
98 | }
99 |
100 | public ReusableLatch(final int count) {
101 | control = new CountSync(count);
102 | }
103 |
104 | public int getCount() {
105 | return control.getCount();
106 | }
107 |
108 | public void setCount(final int count) {
109 | control.setCount(count);
110 | }
111 |
112 | public void countUp() {
113 | control.add();
114 | }
115 |
116 | public void countDown() {
117 | control.releaseShared(1);
118 | }
119 |
120 | public void countDown(final int count) {
121 | control.releaseShared(count);
122 | }
123 |
124 | public void await() throws InterruptedException {
125 | control.acquireSharedInterruptibly(1);
126 | }
127 |
128 | public boolean await(final long milliseconds) throws InterruptedException {
129 | return control.tryAcquireSharedNanos(1, TimeUnit.MILLISECONDS.toNanos(milliseconds));
130 | }
131 |
132 | public boolean await(final long timeWait, TimeUnit timeUnit) throws InterruptedException {
133 | return control.tryAcquireSharedNanos(1, timeUnit.toNanos(timeWait));
134 | }
135 | }
136 |
--------------------------------------------------------------------------------