├── LICENSE ├── README.md ├── build.xml ├── dist ├── mappedbus-0.4.jar ├── mappedbus-0.5.1.jar └── mappedbus-0.5.jar ├── src ├── main │ └── io │ │ └── mappedbus │ │ ├── MappedBusConstants.java │ │ ├── MappedBusMessage.java │ │ ├── MappedBusReader.java │ │ ├── MappedBusWriter.java │ │ ├── MemoryMappedFile.java │ │ └── package-info.java ├── perf │ └── io │ │ └── mappedbus │ │ └── perf │ │ ├── MessageReader.java │ │ ├── MessageWriter.java │ │ └── PriceUpdate.java └── sample │ └── io │ └── mappedbus │ └── sample │ ├── bytearray │ ├── ByteArrayReader.java │ └── ByteArrayWriter.java │ ├── object │ ├── ObjectReader.java │ ├── ObjectWriter.java │ └── PriceUpdate.java │ └── token │ ├── Node.java │ └── Token.java └── test └── io └── mappedbus ├── ByteArrayBasedIntegrityTest.java ├── MappedBusReaderTest.java ├── MappedBusWriterTest.java ├── MemoryMappedFileTest.java ├── MemoryTester.java ├── ObjectBasedIntegrityTest.java ├── RollbackTest.java └── TokenTest.java /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Mappedbus is a Java based high throughput, low latency message bus, using a memory mapped file or shared memory as transport 2 | 3 | Mappedbus was inspired by [Java Chronicle](https://github.com/OpenHFT/Chronicle-Queue) with the main difference that it's designed to efficiently support multiple writers – enabling use cases where the order of messages produced by multiple processes are important. 4 | 5 | The throughput (on a laptop, i7-4558U @ 2.8 GHz) between a single producer writing at full speed and a single consumer is around 14 million messages per second (a small message consisting of three integer fields), and the average read/write latency is around 70 ns per message. 6 | 7 | Mappedbus is a lock-free data structure. 8 | 9 | Mappedbus does not create any objects after startup and therefore has Zero GC impact. 10 | 11 | #### Features: 12 | * IPC between multiple processes by message passing. 13 | * Support for a memory mapped file, or shared memory as transport. 14 | * Support for object or byte array (raw data) based messages. 15 | 16 | ### Getting Started 17 | 18 | Download mappedbus.jar from the release tab (or clone the project and build it from source by running "ant") and try out any of the samples described below. 19 | 20 | ### Usage 21 | 22 | Setup a reader and a writer: 23 | ```java 24 | // Setup a reader 25 | MappedBusReader reader = new MappedBusReader("/tmp/test", 100000L, 32); 26 | reader.open(); 27 | 28 | // Setup a writer 29 | MappedBusWriter writer = new MappedBusWriter("/tmp/test", 100000L, 32); 30 | writer.open(); 31 | ``` 32 | 33 | In the code above the file "/tmp/test" is on disk and thus is memory mapped by the library. To use the library with shared memory, instead point to a file in "/dev/shm", for example, "/dev/shm/test". 34 | 35 | When using a memory mapped file the messages will be lazily persisted to disk. With shared memory the messages will be stored in the RAM. 36 |

37 | Read/write messages using objects: 38 | ```java 39 | PriceUpdate priceUpdate = new PriceUpdate(); 40 | 41 | // write a message 42 | writer.write(priceUpdate); 43 | 44 | // read messages 45 | while (true) { 46 | if (reader.next()) { 47 | int type = reader.readType(); 48 | if (type == 0) { 49 | reader.readMessage(priceUpdate) 50 | } 51 | } 52 | } 53 | ``` 54 | 55 | Read/write messages using byte arrays: 56 | ```java 57 | byte[] buffer = new byte[32]; 58 | 59 | // write a buffer 60 | writer.write(buffer, 0, buffer.length); 61 | 62 | // read buffers 63 | while (true) { 64 | if (reader.next()) { 65 | int length = reader.readBuffer(buffer, 0); 66 | } 67 | } 68 | ``` 69 | 70 | ### Examples 71 | 72 | The project contains examples of an object based and a byte array based reader/writer. 73 | 74 | The object based one works as follows. The ObjectWriter class will send a message, PriceUpdate, which contains three fields: source, price and quantity. The first argument of the ObjectWriter is the source. The ObjectReader simply prints every message it receives. 75 | 76 | ``` 77 | > java -cp mappedbus.jar io.mappedbus.sample.object.ObjectWriter 0 78 | ... 79 | ``` 80 | ``` 81 | > java -cp mappedbus.jar io.mappedbus.sample.object.ObjectWriter 1 82 | ... 83 | ``` 84 | ``` 85 | > java -cp mappedbus.jar io.mappedbus.sample.object.ObjectReader 86 | ... 87 | Read: PriceUpdate [source=0, price=20, quantity=40] 88 | Read: PriceUpdate [source=1, price=8, quantity=16] 89 | Read: PriceUpdate [source=0, price=22, quantity=44] 90 | ``` 91 | 92 | The byte array based example is run in the same way. 93 | 94 | Another example simulates a token being passed around between a number of nodes. Each node will send a message, Token, which contains two fields: to and from. When a node receives a token it will check whether it's the receiver and if so it will send a new token message with the "to" field set to it's id + 1 mod "number of nodes". 95 | ``` 96 | > java -cp mappedbus.jar io.mappedbus.sample.token.Node 0 3 97 | Read: Token [from=0, to=1] 98 | Read: Token [from=1, to=2] 99 | ... 100 | ``` 101 | ``` 102 | > java -cp mappedbus.jar io.mappedbus.sample.token.Node 1 3 103 | Read: Token [from=0, to=1] 104 | Read: Token [from=1, to=2] 105 | ... 106 | ``` 107 | ``` 108 | > java -cp mappedbus.jar io.mappedbus.sample.token.Node 2 3 109 | Read: Token [from=0, to=1] 110 | Read: Token [from=1, to=2] 111 | ... 112 | ``` 113 | 114 | 115 | ### Performance 116 | 117 | The project contains a performance test which can be run as follows: 118 | ``` 119 | > rm -rf /tmp/test;java -cp mappedbus.jar io.mappedbus.perf.MessageReader /tmp/test 120 | ... 121 | Elapsed: 5660 ms 122 | Per op: 70 ns 123 | Op/s: 14131938 124 | ``` 125 | ``` 126 | > java -cp mappedbus.jar io.mappedbus.perf.MessageWriter /tmp/test 127 | ... 128 | ``` 129 | 130 | ### Implementation 131 | 132 | This is how Mappedbus solves the synchronization problem between multiple writers (each running in it's own process/JVM): 133 | 134 | * The first eight bytes of the file make up a field called the limit. This field specifies how much data has been written to the file. The readers will poll the limit field (using volatile) to see whether there's a new record to be read. 135 | 136 | * When a writer adds a record to the file it will use the fetch-and-add instruction to atomically update the limit field. 137 | 138 | * When the limit field has increased a reader will know there's new data to be read, but the writer which updated the limit field might not yet have written any data in the record. To avoid this problem each record contains an initial four bytes which make up the status flag field. The status flag field has three possible values: not set, committed, rolled back. 139 | 140 | * When a writer has finished writing a record it will set the status field to value indicating the record has been committed (using compare and swap) and the reader will only start reading a record once it has seen that the commit field has been set. 141 | 142 | * A writer might crash after it has updated the limit field but before it has updated the status flag field indicating the record has been committed. To avoid this problem the reader has a timeout for how long it will wait for the commit field to be set. When that time is reached the reader will set the status flag field (using compare and swap) to a value indicating the record has been rolled back, and continue with the next record. When the status flag field is set to indicate it's been rolled back the record is always ignored by the readers. 143 | 144 | * A slow writer may write a message and be about to set the status flag to indicate the record has been committed, while a reader has already timed out and set the status flag to indicate the record has been rolled back. Since the status flag is updated using compare-and-swap the writer will detect this, and the write() method call will return false to indicate the write was not successful. 145 | 146 | The solution seems to work well on Linux x86 with Oracle's JVM (1.8) but it probably won't work on all platforms. The project contains a test (called IntegrityTest) to check whether it works on the platform used. 147 | 148 | ### Questions 149 | 150 | For questions or suggestions drop an email to info@mappedbus.io 151 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | MappedBus 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /dist/mappedbus-0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caplogic/Mappedbus/d18b368fa9df19cddb3b0b0eb6937a257740e3d6/dist/mappedbus-0.4.jar -------------------------------------------------------------------------------- /dist/mappedbus-0.5.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caplogic/Mappedbus/d18b368fa9df19cddb3b0b0eb6937a257740e3d6/dist/mappedbus-0.5.1.jar -------------------------------------------------------------------------------- /dist/mappedbus-0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caplogic/Mappedbus/d18b368fa9df19cddb3b0b0eb6937a257740e3d6/dist/mappedbus-0.5.jar -------------------------------------------------------------------------------- /src/main/io/mappedbus/MappedBusConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.mappedbus; 15 | 16 | /** 17 | * Class with constants. 18 | * 19 | */ 20 | public class MappedBusConstants { 21 | 22 | public static class Structure { 23 | 24 | public static final int Limit = 0; 25 | 26 | public static final int Data = Length.Limit; 27 | 28 | } 29 | 30 | public static class Length { 31 | 32 | public static final int Limit = 8; 33 | 34 | public static final int StatusFlag = 4; 35 | 36 | public static final int Metadata = 4; 37 | 38 | public static final int RecordHeader = StatusFlag + Metadata; 39 | 40 | } 41 | 42 | public static class StatusFlag { 43 | 44 | public static final byte NotSet = 0; 45 | 46 | public static final byte Commit = 1; 47 | 48 | public static final byte Rollback = 2; 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/io/mappedbus/MappedBusMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.mappedbus; 15 | 16 | /** 17 | * Interface for messages that can be serialized to the bus. 18 | * 19 | */ 20 | public interface MappedBusMessage { 21 | 22 | /** 23 | * Writes a message to the bus. 24 | * 25 | * @param mem an instance of the memory mapped file 26 | * @param pos the start of the current record 27 | */ 28 | public void write(MemoryMappedFile mem, long pos); 29 | 30 | /** 31 | * Reads a message from the bus. 32 | * 33 | * @param mem an instance of the memory mapped file 34 | * @param pos the start of the current record 35 | */ 36 | public void read(MemoryMappedFile mem, long pos); 37 | 38 | /** 39 | * Returns the message type. 40 | * 41 | * @return the message type 42 | */ 43 | public int type(); 44 | } -------------------------------------------------------------------------------- /src/main/io/mappedbus/MappedBusReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.mappedbus; 15 | 16 | import io.mappedbus.MappedBusConstants.StatusFlag; 17 | import io.mappedbus.MappedBusConstants.Length; 18 | import io.mappedbus.MappedBusConstants.Structure; 19 | 20 | import java.io.EOFException; 21 | import java.io.IOException; 22 | 23 | /** 24 | * Class for reading messages from the bus. 25 | *

26 | * Messages can either be message based or byte array based. 27 | *

28 | * The typical usage is as follows: 29 | *

 30 |  * {@code
 31 |  * // Construct a reader
 32 |  * MappedBusReader reader = new MappedBusReader("/tmp/test", 100000L, 32);
 33 |  * reader.open();
 34 |  * 
 35 |  * // A: read messages as objects
 36 |  * while (true) {
 37 |  *    if (reader.next()) {
 38 |  *       int type = reader.readType();
 39 |  *       if (type == 0) {
 40 |  *          reader.readMessage(priceUpdate)
 41 |  *       }
 42 |  *    }
 43 |  * }
 44 |  *
 45 |  * // B: read messages as byte arrays
 46 |  * while (true) {
 47 |  *    if (reader.next()) {
 48 |  *       int length = reader.read(buffer, 0);
 49 |  *    }
 50 |  * }
 51 |  *
 52 |  * // Close the reader
 53 |  * reader.close();
 54 |  * }
 55 |  * 
56 | */ 57 | public class MappedBusReader { 58 | 59 | protected static final long MAX_TIMEOUT_COUNT = 100; 60 | 61 | private final String fileName; 62 | 63 | private final long fileSize; 64 | 65 | private final int recordSize; 66 | 67 | private MemoryMappedFile mem; 68 | 69 | private long limit = Structure.Data; 70 | 71 | private long prevLimit = 0; 72 | 73 | private long initialLimit; 74 | 75 | private int maxTimeout = 2000; 76 | 77 | protected long timerStart; 78 | 79 | protected long timeoutCounter; 80 | 81 | private boolean typeRead; 82 | 83 | /** 84 | * Constructs a new reader. 85 | * 86 | * @param fileName the name of the memory mapped file 87 | * @param fileSize the maximum size of the file 88 | * @param recordSize the maximum size of a record (excluding status flags and meta data) 89 | */ 90 | public MappedBusReader(String fileName, long fileSize, int recordSize) { 91 | this.fileName = fileName; 92 | this.fileSize = fileSize; 93 | this.recordSize = recordSize; 94 | } 95 | 96 | /** 97 | * Opens the reader. 98 | * 99 | * @throws IOException if there was a problem opening the file 100 | */ 101 | public void open() throws IOException { 102 | try { 103 | mem = new MemoryMappedFile(fileName, fileSize); 104 | } catch(Exception e) { 105 | throw new IOException("Unable to open the file: " + fileName, e); 106 | } 107 | initialLimit = mem.getLongVolatile(Structure.Limit); 108 | } 109 | 110 | /** 111 | * Sets the time for a reader to wait for a record to be committed. 112 | * 113 | * When the timeout occurs the reader will mark the record as "rolled back" and 114 | * the record is ignored. 115 | * 116 | * @param timeout the timeout in milliseconds 117 | */ 118 | public void setTimeout(int timeout) { 119 | this.maxTimeout = timeout; 120 | } 121 | 122 | /** 123 | * Steps forward to the next record if there's one available. 124 | * 125 | * The method has a timeout for how long it will wait for the commit field to be set. When the timeout is 126 | * reached it will set the roll back field and skip over the record. 127 | * 128 | * @return true, if there's a new record available, otherwise false 129 | * @throws EOFException in case the end of the file was reached 130 | */ 131 | public boolean next() throws EOFException { 132 | if (limit >= fileSize) { 133 | throw new EOFException("End of file was reached"); 134 | } 135 | if (prevLimit != 0 && limit - prevLimit < Length.RecordHeader + recordSize) { 136 | limit = prevLimit + Length.RecordHeader + recordSize; 137 | } 138 | if (mem.getLongVolatile(Structure.Limit) <= limit) { 139 | return false; 140 | } 141 | int statusFlag = mem.getIntVolatile(limit); 142 | if (statusFlag == StatusFlag.Rollback) { 143 | limit += Length.RecordHeader + recordSize; 144 | prevLimit = 0; 145 | timeoutCounter = 0; 146 | timerStart = 0; 147 | return false; 148 | } 149 | if (statusFlag == StatusFlag.Commit) { 150 | timeoutCounter = 0; 151 | timerStart = 0; 152 | prevLimit = limit; 153 | return true; 154 | } 155 | timeoutCounter++; 156 | if (timeoutCounter >= MAX_TIMEOUT_COUNT) { 157 | if (timerStart == 0) { 158 | timerStart = System.currentTimeMillis(); 159 | } else { 160 | if (System.currentTimeMillis() - timerStart >= maxTimeout) { 161 | if (!mem.compareAndSwapInt(limit, StatusFlag.NotSet, StatusFlag.Rollback)) { 162 | // there are two cases this can happen 163 | // 1) a slow writer eventually set the status flag to commit 164 | // 2) another reader set the status flag to rollback right before this reader was going to 165 | // in both cases return false, and the value of the status flag will be used in the next call to this method 166 | return false; 167 | } 168 | limit += Length.RecordHeader + recordSize; 169 | prevLimit = 0; 170 | timeoutCounter = 0; 171 | timerStart = 0; 172 | return false; 173 | } 174 | } 175 | } 176 | return false; 177 | } 178 | 179 | /** 180 | * Reads the message type. 181 | * 182 | * @return the message type 183 | */ 184 | public int readType() { 185 | typeRead = true; 186 | limit += Length.StatusFlag; 187 | int type = mem.getInt(limit); 188 | limit += Length.Metadata; 189 | return type; 190 | } 191 | 192 | /** 193 | * Reads the next message. 194 | * 195 | * @param message the message object to populate 196 | * @return the message object 197 | */ 198 | public MappedBusMessage readMessage(MappedBusMessage message) { 199 | if (!typeRead) { 200 | readType(); 201 | } 202 | typeRead = false; 203 | message.read(mem, limit); 204 | limit += recordSize; 205 | return message; 206 | } 207 | 208 | /** 209 | * Reads the next buffer of data. 210 | * 211 | * @param dst the input buffer 212 | * @param offset the offset in the buffer of the first byte to read data into 213 | * @return the length of the record that was read 214 | */ 215 | public int readBuffer(byte[] dst, int offset) { 216 | limit += Length.StatusFlag; 217 | int length = mem.getInt(limit); 218 | limit += Length.Metadata; 219 | mem.getBytes(limit, dst, offset, length); 220 | limit += recordSize; 221 | return length; 222 | } 223 | 224 | /** 225 | * Indicates whether all records available when the reader was created have been read. 226 | * 227 | * @return true, if all records available from the start was read, otherwise false 228 | */ 229 | public boolean hasRecovered() { 230 | return limit >= initialLimit; 231 | } 232 | 233 | /** 234 | * Closes the reader. 235 | * 236 | * @throws IOException if there was an error closing the file 237 | */ 238 | public void close() throws IOException { 239 | try { 240 | mem.unmap(); 241 | } catch(Exception e) { 242 | throw new IOException("Unable to close the file", e); 243 | } 244 | } 245 | } -------------------------------------------------------------------------------- /src/main/io/mappedbus/MappedBusWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.mappedbus; 15 | import io.mappedbus.MappedBusConstants.StatusFlag; 16 | import io.mappedbus.MappedBusConstants.Length; 17 | import io.mappedbus.MappedBusConstants.Structure; 18 | 19 | import java.io.EOFException; 20 | import java.io.IOException; 21 | 22 | /** 23 | * Class for writing messages to the bus. 24 | *

25 | * Messages can either be message based or byte array based. 26 | *

27 | * The typical usage is as follows: 28 | *

 29 |  * {@code
 30 |  * // Construct a writer
 31 |  * MappedBusWriter writer = new MappedBusWriter("/tmp/test", 100000L, 32);
 32 |  * writer.open();
 33 |  * 
 34 |  * // A: write an object based message
 35 |  * PriceUpdate priceUpdate = new PriceUpdate();
 36 |  *
 37 |  * writer.write(priceUpdate);
 38 |  * 
 39 |  * // B: write a byte array based message
 40 |  * byte[] buffer = new byte[32];
 41 |  *
 42 |  * writer.write(buffer, 0, buffer.length);
 43 |  *
 44 |  * // Close the writer
 45 |  * writer.close();
 46 |  * }
 47 |  * 
48 | */ 49 | public class MappedBusWriter { 50 | 51 | private MemoryMappedFile mem; 52 | 53 | private final String fileName; 54 | 55 | private final long fileSize; 56 | 57 | private final int entrySize; 58 | 59 | /** 60 | * Constructs a new writer. 61 | * 62 | * @param fileName the name of the memory mapped file 63 | * @param fileSize the maximum size of the file 64 | * @param recordSize the maximum size of a record (excluding status flags and meta data) 65 | */ 66 | public MappedBusWriter(String fileName, long fileSize, int recordSize) { 67 | this.fileName = fileName; 68 | this.fileSize = fileSize; 69 | this.entrySize = recordSize + Length.RecordHeader; 70 | } 71 | 72 | /** 73 | * Opens the writer. 74 | * 75 | * @throws IOException if there was an error opening the file 76 | */ 77 | public void open() throws IOException { 78 | try { 79 | mem = new MemoryMappedFile(fileName, fileSize); 80 | } catch(Exception e) { 81 | throw new IOException("Unable to open the file: " + fileName, e); 82 | } 83 | mem.compareAndSwapLong(Structure.Limit, 0, Structure.Data); 84 | } 85 | 86 | /** 87 | * Writes a message. 88 | * 89 | * @param message the message object to write 90 | * @return returns true if the message could be written, or otherwise false 91 | * @throws EOFException in case the end of the file was reached 92 | */ 93 | public boolean write(MappedBusMessage message) throws EOFException { 94 | long commitPos = writeRecord(message); 95 | return commit(commitPos); 96 | } 97 | 98 | protected long writeRecord(MappedBusMessage message) throws EOFException { 99 | long limit = allocate(); 100 | long commitPos = limit; 101 | limit += Length.StatusFlag; 102 | mem.putInt(limit, message.type()); 103 | limit += Length.Metadata; 104 | message.write(mem, limit); 105 | return commitPos; 106 | } 107 | 108 | /** 109 | * Writes a buffer of data. 110 | * 111 | * @param src the output buffer 112 | * @param offset the offset in the buffer of the first byte to write 113 | * @param length the length of the data 114 | * @return returns true if the message could be written, or otherwise false 115 | * @throws EOFException in case the end of the file was reached 116 | */ 117 | public boolean write(byte[] src, int offset, int length) throws EOFException { 118 | long commitPos = writeRecord(src, offset, length); 119 | return commit(commitPos); 120 | } 121 | 122 | protected long writeRecord(byte[] src, int offset, int length) throws EOFException { 123 | long limit = allocate(); 124 | long commitPos = limit; 125 | limit += Length.StatusFlag; 126 | mem.putInt(limit, length); 127 | limit += Length.Metadata; 128 | mem.setBytes(limit, src, offset, length); 129 | return commitPos; 130 | } 131 | 132 | private long allocate() throws EOFException { 133 | long limit = mem.getAndAddLong(Structure.Limit, entrySize); 134 | if (limit + entrySize > fileSize) { 135 | throw new EOFException("End of file was reached"); 136 | } 137 | return limit; 138 | } 139 | 140 | protected boolean commit(long commitPos) { 141 | return mem.compareAndSwapInt(commitPos, StatusFlag.NotSet, StatusFlag.Commit); 142 | } 143 | 144 | /** 145 | * Closes the writer. 146 | * 147 | * @throws IOException if there was an error closing the file 148 | */ 149 | public void close() throws IOException { 150 | try { 151 | mem.unmap(); 152 | } catch(Exception e) { 153 | throw new IOException("Unable to close the file", e); 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /src/main/io/mappedbus/MemoryMappedFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This class was inspired from an entry in Bryce Nyeggen's blog 3 | */ 4 | package io.mappedbus; 5 | import java.io.RandomAccessFile; 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Method; 8 | import java.nio.channels.FileChannel; 9 | 10 | import sun.nio.ch.FileChannelImpl; 11 | import sun.misc.Unsafe; 12 | 13 | /** 14 | * Class for direct access to a memory mapped file. 15 | * 16 | */ 17 | @SuppressWarnings("restriction") 18 | public class MemoryMappedFile { 19 | 20 | private static final Unsafe unsafe; 21 | private static final Method mmap; 22 | private static final Method unmmap; 23 | private static final int BYTE_ARRAY_OFFSET; 24 | 25 | private long addr, size; 26 | private final String loc; 27 | 28 | static { 29 | try { 30 | Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); 31 | singleoneInstanceField.setAccessible(true); 32 | unsafe = (Unsafe) singleoneInstanceField.get(null); 33 | mmap = getMethod(FileChannelImpl.class, "map0", int.class, long.class, long.class); 34 | unmmap = getMethod(FileChannelImpl.class, "unmap0", long.class, long.class); 35 | BYTE_ARRAY_OFFSET = unsafe.arrayBaseOffset(byte[].class); 36 | } catch (Exception e) { 37 | throw new RuntimeException(e); 38 | } 39 | } 40 | 41 | private static Method getMethod(Class cls, String name, Class... params) throws Exception { 42 | Method m = cls.getDeclaredMethod(name, params); 43 | m.setAccessible(true); 44 | return m; 45 | } 46 | 47 | private static long roundTo4096(long i) { 48 | return (i + 0xfffL) & ~0xfffL; 49 | } 50 | 51 | private void mapAndSetOffset() throws Exception { 52 | final RandomAccessFile backingFile = new RandomAccessFile(this.loc, "rw"); 53 | backingFile.setLength(this.size); 54 | final FileChannel ch = backingFile.getChannel(); 55 | this.addr = (long) mmap.invoke(ch, 1, 0L, this.size); 56 | ch.close(); 57 | backingFile.close(); 58 | } 59 | 60 | /** 61 | * Constructs a new memory mapped file. 62 | * @param loc the file name 63 | * @param len the file length 64 | * @throws Exception in case there was an error creating the memory mapped file 65 | */ 66 | protected MemoryMappedFile(final String loc, long len) throws Exception { 67 | this.loc = loc; 68 | this.size = roundTo4096(len); 69 | mapAndSetOffset(); 70 | } 71 | 72 | protected void unmap() throws Exception { 73 | unmmap.invoke(null, addr, this.size); 74 | } 75 | 76 | /** 77 | * Reads a byte from the specified position. 78 | * @param pos the position in the memory mapped file 79 | * @return the value read 80 | */ 81 | public byte getByte(long pos) { 82 | return unsafe.getByte(pos + addr); 83 | } 84 | 85 | /** 86 | * Reads a byte (volatile) from the specified position. 87 | * @param pos the position in the memory mapped file 88 | * @return the value read 89 | */ 90 | protected byte getByteVolatile(long pos) { 91 | return unsafe.getByteVolatile(null, pos + addr); 92 | } 93 | 94 | /** 95 | * Reads an int from the specified position. 96 | * @param pos the position in the memory mapped file 97 | * @return the value read 98 | */ 99 | public int getInt(long pos) { 100 | return unsafe.getInt(pos + addr); 101 | } 102 | 103 | /** 104 | * Reads an int (volatile) from the specified position. 105 | * @param pos position in the memory mapped file 106 | * @return the value read 107 | */ 108 | protected int getIntVolatile(long pos) { 109 | return unsafe.getIntVolatile(null, pos + addr); 110 | } 111 | 112 | /** 113 | * Reads a long from the specified position. 114 | * @param pos position in the memory mapped file 115 | * @return the value read 116 | */ 117 | public long getLong(long pos) { 118 | return unsafe.getLong(pos + addr); 119 | } 120 | 121 | /** 122 | * Reads a long (volatile) from the specified position. 123 | * @param pos position in the memory mapped file 124 | * @return the value read 125 | */ 126 | protected long getLongVolatile(long pos) { 127 | return unsafe.getLongVolatile(null, pos + addr); 128 | } 129 | 130 | /** 131 | * Writes a byte to the specified position. 132 | * @param pos the position in the memory mapped file 133 | * @param val the value to write 134 | */ 135 | public void putByte(long pos, byte val) { 136 | unsafe.putByte(pos + addr, val); 137 | } 138 | 139 | /** 140 | * Writes a byte (volatile) to the specified position. 141 | * @param pos the position in the memory mapped file 142 | * @param val the value to write 143 | */ 144 | protected void putByteVolatile(long pos, byte val) { 145 | unsafe.putByteVolatile(null, pos + addr, val); 146 | } 147 | 148 | /** 149 | * Writes an int to the specified position. 150 | * @param pos the position in the memory mapped file 151 | * @param val the value to write 152 | */ 153 | public void putInt(long pos, int val) { 154 | unsafe.putInt(pos + addr, val); 155 | } 156 | 157 | /** 158 | * Writes an int (volatile) to the specified position. 159 | * @param pos the position in the memory mapped file 160 | * @param val the value to write 161 | */ 162 | protected void putIntVolatile(long pos, int val) { 163 | unsafe.putIntVolatile(null, pos + addr, val); 164 | } 165 | 166 | /** 167 | * Writes a long to the specified position. 168 | * @param pos the position in the memory mapped file 169 | * @param val the value to write 170 | */ 171 | public void putLong(long pos, long val) { 172 | unsafe.putLong(pos + addr, val); 173 | } 174 | 175 | /** 176 | * Writes a long (volatile) to the specified position. 177 | * @param pos the position in the memory mapped file 178 | * @param val the value to write 179 | */ 180 | protected void putLongVolatile(long pos, long val) { 181 | unsafe.putLongVolatile(null, pos + addr, val); 182 | } 183 | 184 | /** 185 | * Reads a buffer of data. 186 | * @param pos the position in the memory mapped file 187 | * @param data the input buffer 188 | * @param offset the offset in the buffer of the first byte to read data into 189 | * @param length the length of the data 190 | */ 191 | public void getBytes(long pos, byte[] data, int offset, int length) { 192 | unsafe.copyMemory(null, pos + addr, data, BYTE_ARRAY_OFFSET + offset, length); 193 | } 194 | 195 | /** 196 | * Writes a buffer of data. 197 | * @param pos the position in the memory mapped file 198 | * @param data the output buffer 199 | * @param offset the offset in the buffer of the first byte to write 200 | * @param length the length of the data 201 | */ 202 | public void setBytes(long pos, byte[] data, int offset, int length) { 203 | unsafe.copyMemory(data, BYTE_ARRAY_OFFSET + offset, null, pos + addr, length); 204 | } 205 | 206 | protected boolean compareAndSwapInt(long pos, int expected, int value) { 207 | return unsafe.compareAndSwapInt(null, pos + addr, expected, value); 208 | } 209 | 210 | protected boolean compareAndSwapLong(long pos, long expected, long value) { 211 | return unsafe.compareAndSwapLong(null, pos + addr, expected, value); 212 | } 213 | 214 | protected long getAndAddLong(long pos, long delta) { 215 | return unsafe.getAndAddLong(null, pos + addr, delta); 216 | } 217 | } -------------------------------------------------------------------------------- /src/main/io/mappedbus/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Mappedbus is a Java based high throughput, low latency message bus, using either a memory mapped file or shared memory as transport 3 | * 4 | */ 5 | package io.mappedbus; -------------------------------------------------------------------------------- /src/perf/io/mappedbus/perf/MessageReader.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.perf; 2 | import io.mappedbus.MappedBusReader; 3 | import io.mappedbus.MappedBusMessage; 4 | 5 | public class MessageReader { 6 | 7 | public static void main(String[] args) { 8 | MessageReader reader = new MessageReader(); 9 | reader.run(args[0]); 10 | } 11 | 12 | public void run(String fileName) { 13 | try { 14 | MappedBusReader reader = new MappedBusReader(fileName, 20000000000L, 12); 15 | reader.open(); 16 | 17 | PriceUpdate priceUpdate = new PriceUpdate(); 18 | 19 | MappedBusMessage message = null; 20 | 21 | System.out.println("Waiting for first message"); 22 | 23 | long start = 0; 24 | for (int i = 0; i < 80000000; i++) { 25 | while (true) { 26 | if (reader.next()) { 27 | if (start == 0) { 28 | start = System.nanoTime(); 29 | System.out.println("Got first message"); 30 | } 31 | int type = reader.readType(); 32 | switch (type) { 33 | case PriceUpdate.TYPE: 34 | message = priceUpdate; 35 | break; 36 | default: 37 | throw new RuntimeException("Unknown type: " + type); 38 | } 39 | reader.readMessage(message); 40 | break; 41 | } 42 | } 43 | } 44 | long stop = System.nanoTime(); 45 | System.out.println("Elapsed: " + ((stop - start) / 1000000 ) + " ms"); 46 | System.out.println("Per op: " + ((stop - start) / 80000000 ) + " ns"); 47 | System.out.println("Op/s: " + (long)(80000000/((stop-start)/(float)1000000000))); 48 | 49 | } catch(Exception e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/perf/io/mappedbus/perf/MessageWriter.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.perf; 2 | import io.mappedbus.MappedBusWriter; 3 | 4 | import java.io.File; 5 | 6 | public class MessageWriter { 7 | 8 | public static void main(String[] args) { 9 | MessageWriter writer = new MessageWriter(); 10 | writer.run(args[0]); 11 | } 12 | 13 | public void run(String fileName) { 14 | try { 15 | MappedBusWriter writer = new MappedBusWriter(fileName, 20000000000L, 12); 16 | writer.open(); 17 | 18 | PriceUpdate priceUpdate = new PriceUpdate(); 19 | 20 | for (int i = 0; i < 80000000; i++) { 21 | writer.write(priceUpdate); 22 | } 23 | 24 | System.out.println("Done"); 25 | } catch(Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/perf/io/mappedbus/perf/PriceUpdate.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.perf; 2 | import io.mappedbus.MemoryMappedFile; 3 | import io.mappedbus.MappedBusMessage; 4 | 5 | 6 | public class PriceUpdate implements MappedBusMessage { 7 | 8 | public static final int TYPE = 0; 9 | 10 | private int source; 11 | 12 | private int price; 13 | 14 | private int quantity; 15 | 16 | public PriceUpdate() { 17 | } 18 | 19 | public PriceUpdate(int source, int price, int quantity) { 20 | this.source = source; 21 | this.price = price; 22 | this.quantity = quantity; 23 | } 24 | 25 | public int type() { 26 | return TYPE; 27 | } 28 | 29 | public int getSource() { 30 | return source; 31 | } 32 | 33 | public void setSource(int source) { 34 | this.source = source; 35 | } 36 | 37 | public int getPrice() { 38 | return price; 39 | } 40 | 41 | public void setPrice(int price) { 42 | this.price = price; 43 | } 44 | 45 | public int getQuantity() { 46 | return quantity; 47 | } 48 | 49 | public void setQuantity(int quantity) { 50 | this.quantity = quantity; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "PriceUpdate [source=" + source + ", price=" + price + ", quantity=" + quantity + "]"; 56 | } 57 | 58 | public void write(MemoryMappedFile mem, long pos) { 59 | mem.putInt(pos, source); 60 | mem.putInt(pos + 4, price); 61 | mem.putInt(pos + 8, quantity); 62 | } 63 | 64 | public void read(MemoryMappedFile mem, long pos) { 65 | source = mem.getInt(pos); 66 | price = mem.getInt(pos + 4); 67 | quantity = mem.getInt(pos + 8); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/sample/io/mappedbus/sample/bytearray/ByteArrayReader.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.sample.bytearray; 2 | import io.mappedbus.MappedBusReader; 3 | 4 | import java.util.Arrays; 5 | 6 | public class ByteArrayReader { 7 | 8 | public static void main(String[] args) { 9 | ByteArrayReader reader = new ByteArrayReader(); 10 | reader.run(); 11 | } 12 | 13 | public void run() { 14 | try { 15 | MappedBusReader reader = new MappedBusReader("/tmp/test-bytearray", 2000000L, 10); 16 | reader.open(); 17 | 18 | byte[] buffer = new byte[10]; 19 | 20 | while (true) { 21 | if (reader.next()) { 22 | int length = reader.readBuffer(buffer, 0); 23 | System.out.println("Read: length = " + length + ", data= "+ Arrays.toString(buffer)); 24 | } 25 | } 26 | } catch(Exception e) { 27 | e.printStackTrace(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/sample/io/mappedbus/sample/bytearray/ByteArrayWriter.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.sample.bytearray; 2 | import io.mappedbus.MappedBusWriter; 3 | 4 | import java.util.Arrays; 5 | 6 | public class ByteArrayWriter { 7 | 8 | public static void main(String[] args) { 9 | ByteArrayWriter writer = new ByteArrayWriter(); 10 | writer.run(Integer.valueOf(args[0])); 11 | } 12 | 13 | public void run(int source) { 14 | try { 15 | MappedBusWriter writer = new MappedBusWriter("/tmp/test-bytearray", 2000000L, 10); 16 | writer.open(); 17 | 18 | byte[] buffer = new byte[10]; 19 | 20 | for (int i = 0; i < 1000; i++) { 21 | Arrays.fill(buffer, (byte)source); 22 | writer.write(buffer, 0, buffer.length); 23 | Thread.sleep(1000); 24 | } 25 | } catch(Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/sample/io/mappedbus/sample/object/ObjectReader.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.sample.object; 2 | import io.mappedbus.MappedBusReader; 3 | import io.mappedbus.MappedBusMessage; 4 | 5 | public class ObjectReader { 6 | 7 | public static void main(String[] args) { 8 | ObjectReader reader = new ObjectReader(); 9 | reader.run(); 10 | } 11 | 12 | public void run() { 13 | try { 14 | MappedBusReader reader = new MappedBusReader("/tmp/test-message", 2000000L, 12); 15 | reader.open(); 16 | 17 | PriceUpdate priceUpdate = new PriceUpdate(); 18 | 19 | MappedBusMessage message = null; 20 | 21 | while (true) { 22 | if (reader.next()) { 23 | boolean recovered = reader.hasRecovered(); 24 | int type = reader.readType(); 25 | switch (type) { 26 | case PriceUpdate.TYPE: 27 | message = priceUpdate; 28 | break; 29 | default: 30 | throw new RuntimeException("Unknown type: " + type); 31 | } 32 | reader.readMessage(message); 33 | System.out.println("Read: " + message + ", hasRecovered=" + recovered); 34 | } 35 | } 36 | } catch(Exception e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/sample/io/mappedbus/sample/object/ObjectWriter.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.sample.object; 2 | import io.mappedbus.MappedBusWriter; 3 | 4 | public class ObjectWriter { 5 | 6 | public static void main(String[] args) { 7 | ObjectWriter writer = new ObjectWriter(); 8 | writer.run(Integer.valueOf(args[0])); 9 | } 10 | 11 | public void run(int source) { 12 | try { 13 | MappedBusWriter writer = new MappedBusWriter("/tmp/test-message", 2000000L, 12); 14 | writer.open(); 15 | 16 | PriceUpdate priceUpdate = new PriceUpdate(); 17 | 18 | for (int i = 0; i < 1000; i++) { 19 | priceUpdate.setSource(source); 20 | priceUpdate.setPrice(i * 2); 21 | priceUpdate.setQuantity(i * 4); 22 | writer.write(priceUpdate); 23 | Thread.sleep(1000); 24 | } 25 | } catch(Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/sample/io/mappedbus/sample/object/PriceUpdate.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.sample.object; 2 | import io.mappedbus.MemoryMappedFile; 3 | import io.mappedbus.MappedBusMessage; 4 | 5 | 6 | public class PriceUpdate implements MappedBusMessage { 7 | 8 | public static final int TYPE = 0; 9 | 10 | private int source; 11 | 12 | private int price; 13 | 14 | private int quantity; 15 | 16 | public PriceUpdate() { 17 | } 18 | 19 | public PriceUpdate(int source, int price, int quantity) { 20 | this.source = source; 21 | this.price = price; 22 | this.quantity = quantity; 23 | } 24 | 25 | public int type() { 26 | return TYPE; 27 | } 28 | 29 | public int getSource() { 30 | return source; 31 | } 32 | 33 | public void setSource(int source) { 34 | this.source = source; 35 | } 36 | 37 | public int getPrice() { 38 | return price; 39 | } 40 | 41 | public void setPrice(int price) { 42 | this.price = price; 43 | } 44 | 45 | public int getQuantity() { 46 | return quantity; 47 | } 48 | 49 | public void setQuantity(int quantity) { 50 | this.quantity = quantity; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "PriceUpdate [source=" + source + ", price=" + price + ", quantity=" + quantity + "]"; 56 | } 57 | 58 | public void write(MemoryMappedFile mem, long pos) { 59 | mem.putInt(pos, source); 60 | mem.putInt(pos + 4, price); 61 | mem.putInt(pos + 8, quantity); 62 | } 63 | 64 | public void read(MemoryMappedFile mem, long pos) { 65 | source = mem.getInt(pos); 66 | price = mem.getInt(pos + 4); 67 | quantity = mem.getInt(pos + 8); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/sample/io/mappedbus/sample/token/Node.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.sample.token; 2 | import io.mappedbus.MappedBusReader; 3 | import io.mappedbus.MappedBusWriter; 4 | import io.mappedbus.MappedBusMessage; 5 | 6 | public class Node { 7 | 8 | private MappedBusReader reader; 9 | 10 | private MappedBusWriter writer; 11 | 12 | public static void main(String[] args) throws Exception { 13 | Node reader = new Node(); 14 | reader.init(); 15 | reader.run(Integer.valueOf(args[0]), Integer.valueOf(args[1])); 16 | } 17 | 18 | public void init() throws Exception { 19 | writer = new MappedBusWriter("/tmp/token-test", 2000000L, 8); 20 | writer.open(); 21 | 22 | reader = new MappedBusReader("/tmp/token-test", 2000000L, 8); 23 | reader.open(); 24 | } 25 | 26 | public void run(int id, int numberOfNodes) { 27 | try { 28 | Token token = new Token(); 29 | 30 | if (id == 0) { 31 | token.setFrom(id); 32 | token.setTo(1); 33 | writer.write(token); 34 | } 35 | 36 | while (true) { 37 | if (reader.next()) { 38 | System.out.println("Read: " + reader.readMessage(token)); 39 | 40 | if (token.getTo() == id) { 41 | Thread.sleep(1000); 42 | token.setFrom(id); 43 | token.setTo((id + 1) % numberOfNodes); 44 | writer.write(token); 45 | } 46 | } 47 | } 48 | } catch(Exception e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/sample/io/mappedbus/sample/token/Token.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus.sample.token; 2 | import io.mappedbus.MemoryMappedFile; 3 | import io.mappedbus.MappedBusMessage; 4 | 5 | 6 | public class Token implements MappedBusMessage { 7 | 8 | public static final int TYPE = 0; 9 | 10 | private int from; 11 | 12 | private int to; 13 | 14 | public Token() { 15 | } 16 | 17 | public Token(int source, int target) { 18 | this.from = source; 19 | this.to = target; 20 | } 21 | 22 | public int type() { 23 | return TYPE; 24 | } 25 | 26 | public int getFrom() { 27 | return from; 28 | } 29 | 30 | public void setFrom(int from) { 31 | this.from = from; 32 | } 33 | 34 | public int getTo() { 35 | return to; 36 | } 37 | 38 | public void setTo(int to) { 39 | this.to = to; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Token [from=" + from + ", to=" + to + "]"; 45 | } 46 | 47 | public void write(MemoryMappedFile mem, long pos) { 48 | mem.putInt(pos, from); 49 | mem.putInt(pos + 4, to); 50 | } 51 | 52 | public void read(MemoryMappedFile mem, long pos) { 53 | from = mem.getInt(pos); 54 | to = mem.getInt(pos + 4); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /test/io/mappedbus/ByteArrayBasedIntegrityTest.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import io.mappedbus.MappedBusReader; 4 | import io.mappedbus.MappedBusWriter; 5 | 6 | import java.io.File; 7 | import java.util.Arrays; 8 | 9 | import org.junit.After; 10 | import org.junit.Assert; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import static org.junit.Assert.fail; 15 | import static org.junit.Assert.assertEquals; 16 | 17 | /** 18 | * This class tests that records written by multiple concurrent writers are stored correctly. 19 | * 20 | * A number of writers are started that each run in their own thread. Each writer add records with 21 | * data specific for that thread: thread one writes records with a single byte with value one and length one, 22 | * thread two writes records with two bytes both set to the value two, and so on. 23 | * 24 | * Concurrently a reader goes through the file to check that the records received have the correct content 25 | * and length. 26 | * 27 | * For more exhaustive testing NUM_RUNS can be increased. 28 | * 29 | */ 30 | public class ByteArrayBasedIntegrityTest { 31 | 32 | public static final String FILE_NAME = "/tmp/bytearraybased-integrity-test"; 33 | 34 | public static final long FILE_SIZE = 4000000L; 35 | 36 | public static final int NUM_WRITERS = 9; 37 | 38 | public static final int RECORD_LENGTH = 10; 39 | 40 | public static final int NUM_RECORDS = 10000; 41 | 42 | public static final int NUM_RUNS = 1000; 43 | 44 | @Before public void before() { 45 | new File(FILE_NAME).delete(); 46 | } 47 | 48 | @After public void after() { 49 | new File(FILE_NAME).delete(); 50 | } 51 | 52 | @Test public void test() throws Exception { 53 | for (int i = 0; i < NUM_RUNS; i++) { 54 | runTest(); 55 | } 56 | } 57 | 58 | private void runTest() throws Exception { 59 | new File(FILE_NAME).delete(); 60 | 61 | Writer[] writers = new Writer[NUM_WRITERS]; 62 | for (int i = 0; i < writers.length; i++) { 63 | writers[i] = new Writer(i + 1); 64 | } 65 | for (int i = 0; i < writers.length; i++) { 66 | writers[i].start(); 67 | } 68 | 69 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_LENGTH); 70 | reader.open(); 71 | 72 | int records = 0; 73 | byte[] data = new byte[RECORD_LENGTH]; 74 | while (true) { 75 | if (reader.next()) { 76 | int length = reader.readBuffer(data, 0); 77 | Assert.assertEquals(data[0], length); 78 | for (int i=0; i < length; i++) { 79 | if (data[0] != data[i]) { 80 | fail(); 81 | return; 82 | } 83 | } 84 | records++; 85 | if (records >= NUM_RECORDS * NUM_WRITERS) { 86 | break; 87 | } 88 | } 89 | } 90 | 91 | assertEquals(NUM_RECORDS * NUM_WRITERS, records); 92 | 93 | reader.close(); 94 | } 95 | 96 | class Writer extends Thread { 97 | 98 | private final int id; 99 | 100 | public Writer(int id) { 101 | this.id = id; 102 | } 103 | 104 | public void run() { 105 | try { 106 | MappedBusWriter writer = new MappedBusWriter(ByteArrayBasedIntegrityTest.FILE_NAME, ByteArrayBasedIntegrityTest.FILE_SIZE, ByteArrayBasedIntegrityTest.RECORD_LENGTH); 107 | writer.open(); 108 | 109 | byte[] data = new byte[ByteArrayBasedIntegrityTest.RECORD_LENGTH]; 110 | Arrays.fill(data, (byte)id); 111 | 112 | for (int i=0; i < ByteArrayBasedIntegrityTest.NUM_RECORDS; i++) { 113 | writer.write(data, 0, id); 114 | } 115 | writer.close(); 116 | } catch(Exception e) { 117 | e.printStackTrace(); 118 | } 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /test/io/mappedbus/MappedBusReaderTest.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertTrue; 6 | import io.mappedbus.MappedBusConstants.StatusFlag; 7 | import io.mappedbus.MappedBusConstants.Length; 8 | import io.mappedbus.MappedBusConstants.Structure; 9 | 10 | import java.io.EOFException; 11 | import java.io.File; 12 | 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | /** 18 | * This class tests MappedBusReader. 19 | * 20 | */ 21 | public class MappedBusReaderTest { 22 | 23 | public static final String FILE_NAME = "/tmp/MappedBusWriterTest"; 24 | 25 | public static final long FILE_SIZE = 1000; 26 | 27 | public static final int RECORD_SIZE = 12; 28 | 29 | @Before public void before() { 30 | new File(FILE_NAME).delete(); 31 | } 32 | 33 | @After public void after() { 34 | new File(FILE_NAME).delete(); 35 | } 36 | 37 | @Test public void testReadEmptyFile() throws Exception { 38 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_SIZE); 39 | reader.open(); 40 | assertEquals(false, reader.next()); 41 | } 42 | 43 | @Test(expected=EOFException.class) public void testReadEOF() throws Exception { 44 | int fileSize = Length.Limit + Length.RecordHeader + RECORD_SIZE; 45 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, fileSize, RECORD_SIZE); 46 | writer.open(); 47 | MappedBusReader reader = new MappedBusReader(FILE_NAME, fileSize, RECORD_SIZE); 48 | reader.open(); 49 | byte[] data = new byte[RECORD_SIZE]; 50 | writer.write(data, 0, data.length); 51 | assertEquals(true, reader.next()); 52 | assertEquals(true, reader.hasRecovered()); 53 | assertEquals(RECORD_SIZE, reader.readBuffer(data, 0)); 54 | reader.next(); // throws EOFException 55 | } 56 | 57 | @Test public void testReadBuffer() throws Exception { 58 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 59 | writer.open(); 60 | 61 | byte[] data1 = {0, 1, 2, 3}; 62 | writer.write(data1, 0, data1.length); 63 | 64 | byte[] data2 = {4, 5, 6}; 65 | writer.write(data2, 0, data2.length); 66 | 67 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_SIZE); 68 | reader.open(); 69 | 70 | byte[] buffer = new byte[4]; 71 | assertEquals(true, reader.next()); 72 | assertEquals(false, reader.hasRecovered()); 73 | assertEquals(4, reader.readBuffer(buffer, 0)); 74 | assertArrayEquals(data1, buffer); 75 | 76 | buffer = new byte[3]; 77 | assertEquals(true, reader.next()); 78 | assertEquals(false, reader.hasRecovered()); 79 | assertEquals(3, reader.readBuffer(buffer, 0)); 80 | assertArrayEquals(data2, buffer); 81 | 82 | assertEquals(false, reader.next()); 83 | assertEquals(true, reader.hasRecovered()); 84 | } 85 | 86 | @Test public void testReadMessage() throws Exception { 87 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 88 | writer.open(); 89 | 90 | PriceUpdate priceUpdate = new PriceUpdate(0, 1, 2); 91 | writer.write(priceUpdate); 92 | 93 | priceUpdate = new PriceUpdate(3, 4, 5); 94 | writer.write(priceUpdate); 95 | 96 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_SIZE); 97 | reader.open(); 98 | 99 | assertEquals(true, reader.next()); 100 | assertEquals(false, reader.hasRecovered()); 101 | assertEquals(0, reader.readType()); 102 | reader.readMessage(priceUpdate); 103 | assertEquals(0, priceUpdate.getSource()); 104 | assertEquals(1, priceUpdate.getPrice()); 105 | assertEquals(2, priceUpdate.getQuantity()); 106 | 107 | assertEquals(true, reader.next()); 108 | assertEquals(false, reader.hasRecovered()); 109 | assertEquals(0, reader.readType()); 110 | reader.readMessage(priceUpdate); 111 | assertEquals(3, priceUpdate.getSource()); 112 | assertEquals(4, priceUpdate.getPrice()); 113 | assertEquals(5, priceUpdate.getQuantity()); 114 | 115 | assertEquals(false, reader.next()); 116 | assertEquals(true, reader.hasRecovered()); 117 | } 118 | 119 | @Test public void testCrashBeforeCommitRollbackBySameReader() throws Exception { 120 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 121 | writer.open(); 122 | 123 | // write first record 124 | PriceUpdate priceUpdate = new PriceUpdate(0, 1, 2); 125 | writer.write(priceUpdate); 126 | 127 | // write second record 128 | priceUpdate = new PriceUpdate(3, 4, 5); 129 | writer.write(priceUpdate); 130 | 131 | // set commit flag to false for the first record 132 | MemoryMappedFile mem = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 133 | mem.putIntVolatile(Structure.Data, 0); 134 | 135 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_SIZE); 136 | reader.setTimeout(0); 137 | reader.open(); 138 | 139 | assertEquals(0, reader.timeoutCounter); 140 | assertEquals(0, reader.timerStart); 141 | for (int i = 0; i < MappedBusReader.MAX_TIMEOUT_COUNT - 1; i++) { 142 | assertEquals(false, reader.next()); 143 | } 144 | assertEquals(99, reader.timeoutCounter); 145 | assertEquals(0, reader.timerStart); 146 | 147 | // the reader starts the timer 148 | assertEquals(false, reader.next()); 149 | assertEquals(false, reader.hasRecovered()); 150 | assertEquals(100, reader.timeoutCounter); 151 | assertTrue(reader.timerStart > 0); 152 | 153 | // the reader sets the roll back flag and skips the record 154 | assertEquals(false, reader.next()); 155 | assertEquals(false, reader.hasRecovered()); 156 | assertEquals(0, reader.timeoutCounter); 157 | assertEquals(0, reader.timerStart); 158 | 159 | // the reader reads the second record 160 | assertEquals(true, reader.next()); 161 | assertEquals(false, reader.hasRecovered()); 162 | assertEquals(0, reader.readType()); 163 | reader.readMessage(priceUpdate); 164 | assertEquals(3, priceUpdate.getSource()); 165 | assertEquals(4, priceUpdate.getPrice()); 166 | assertEquals(5, priceUpdate.getQuantity()); 167 | 168 | // no more records available 169 | assertEquals(false, reader.next()); 170 | assertEquals(true, reader.hasRecovered()); 171 | } 172 | 173 | @Test public void testCrashBeforeCommitRollbackByDifferentReaderBefore() throws Exception { 174 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 175 | writer.open(); 176 | 177 | // write first record 178 | PriceUpdate priceUpdate = new PriceUpdate(0, 1, 2); 179 | writer.write(priceUpdate); 180 | 181 | // write second record 182 | priceUpdate = new PriceUpdate(3, 4, 5); 183 | writer.write(priceUpdate); 184 | 185 | // set commit flag to false for the first record 186 | MemoryMappedFile mem = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 187 | mem.putByteVolatile(Structure.Data, StatusFlag.NotSet); 188 | 189 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_SIZE); 190 | reader.setTimeout(0); 191 | reader.open(); 192 | 193 | assertEquals(0, reader.timeoutCounter); 194 | assertEquals(0, reader.timerStart); 195 | for (int i = 0; i < MappedBusReader.MAX_TIMEOUT_COUNT - 10; i++) { 196 | assertEquals(false, reader.next()); 197 | } 198 | assertEquals(MappedBusReader.MAX_TIMEOUT_COUNT - 10, reader.timeoutCounter); 199 | assertEquals(0, reader.timerStart); 200 | 201 | // another reader sets the rollback flag 202 | mem.putByteVolatile(Structure.Data, StatusFlag.Rollback); 203 | 204 | // the reader skips the record 205 | assertEquals(false, reader.next()); 206 | assertEquals(false, reader.hasRecovered()); 207 | assertEquals(0, reader.timeoutCounter); 208 | assertEquals(0, reader.timerStart); 209 | 210 | // the reader reads the second record 211 | assertEquals(true, reader.next()); 212 | assertEquals(false, reader.hasRecovered()); 213 | assertEquals(0, reader.readType()); 214 | reader.readMessage(priceUpdate); 215 | assertEquals(3, priceUpdate.getSource()); 216 | assertEquals(4, priceUpdate.getPrice()); 217 | assertEquals(5, priceUpdate.getQuantity()); 218 | 219 | // no more records available 220 | assertEquals(false, reader.next()); 221 | assertEquals(true, reader.hasRecovered()); 222 | } 223 | 224 | class PriceUpdate implements MappedBusMessage { 225 | 226 | public static final int TYPE = 0; 227 | 228 | private int source; 229 | 230 | private int price; 231 | 232 | private int quantity; 233 | 234 | public PriceUpdate() { 235 | } 236 | 237 | public PriceUpdate(int source, int price, int quantity) { 238 | this.source = source; 239 | this.price = price; 240 | this.quantity = quantity; 241 | } 242 | 243 | public int type() { 244 | return TYPE; 245 | } 246 | 247 | public int getSource() { 248 | return source; 249 | } 250 | 251 | public void setSource(int source) { 252 | this.source = source; 253 | } 254 | 255 | public int getPrice() { 256 | return price; 257 | } 258 | 259 | public void setPrice(int price) { 260 | this.price = price; 261 | } 262 | 263 | public int getQuantity() { 264 | return quantity; 265 | } 266 | 267 | public void setQuantity(int quantity) { 268 | this.quantity = quantity; 269 | } 270 | 271 | @Override 272 | public String toString() { 273 | return "PriceUpdate [source=" + source + ", price=" + price + ", quantity=" + quantity + "]"; 274 | } 275 | 276 | public void write(MemoryMappedFile mem, long pos) { 277 | mem.putInt(pos, source); 278 | mem.putInt(pos + 4, price); 279 | mem.putInt(pos + 8, quantity); 280 | } 281 | 282 | public void read(MemoryMappedFile mem, long pos) { 283 | source = mem.getInt(pos); 284 | price = mem.getInt(pos + 4); 285 | quantity = mem.getInt(pos + 8); 286 | } 287 | } 288 | } -------------------------------------------------------------------------------- /test/io/mappedbus/MappedBusWriterTest.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import io.mappedbus.MappedBusWriter; 5 | import io.mappedbus.MemoryMappedFile; 6 | import io.mappedbus.MappedBusConstants.Length; 7 | import io.mappedbus.MappedBusConstants.Structure; 8 | 9 | import java.io.EOFException; 10 | import java.io.File; 11 | 12 | import org.junit.After; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | /** 17 | * This class tests MappedBusWriter. 18 | * 19 | */ 20 | public class MappedBusWriterTest { 21 | 22 | public static final String FILE_NAME = "/tmp/MappedBusWriterTest"; 23 | 24 | public static final long FILE_SIZE = 1000; 25 | 26 | public static final int RECORD_SIZE = 12; 27 | 28 | @Before public void before() { 29 | new File(FILE_NAME).delete(); 30 | } 31 | 32 | @After public void after() { 33 | new File(FILE_NAME).delete(); 34 | } 35 | 36 | @Test(expected=EOFException.class) public void testWriteEOF() throws Exception { 37 | int fileSize = Length.Limit + Length.RecordHeader + RECORD_SIZE - 4; 38 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, fileSize, RECORD_SIZE); 39 | writer.open(); 40 | byte[] data = new byte[RECORD_SIZE]; 41 | writer.write(data, 0, RECORD_SIZE); // throws EOFException 42 | } 43 | 44 | @Test(expected=EOFException.class) public void testWriteEOF2() throws Exception { 45 | int fileSize = Length.Limit + Length.RecordHeader + (2 * RECORD_SIZE) - 4; 46 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, fileSize, RECORD_SIZE); 47 | writer.open(); 48 | byte[] data = new byte[RECORD_SIZE]; 49 | writer.write(data, 0, RECORD_SIZE); 50 | writer.write(data, 0, RECORD_SIZE); // throws EOFException 51 | } 52 | 53 | @Test public void testWriteBuffer() throws Exception { 54 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 55 | writer.open(); 56 | 57 | MemoryMappedFile mem = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 58 | 59 | byte[] data1 = {0, 1, 2, 3}; 60 | writer.write(data1, 0, data1.length); 61 | assertEquals(Structure.Data + Length.StatusFlag + Length.Metadata + RECORD_SIZE , mem.getLongVolatile(Structure.Limit)); 62 | 63 | byte[] data2 = {4, 5, 6}; 64 | writer.write(data2, 0, data2.length); 65 | assertEquals(Structure.Data + 2 * (Length.StatusFlag + Length.Metadata + RECORD_SIZE), mem.getLongVolatile(Structure.Limit)); 66 | } 67 | 68 | @Test public void testWriteMessage() throws Exception { 69 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 70 | writer.open(); 71 | 72 | MemoryMappedFile mem = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 73 | 74 | PriceUpdate priceUpdate = new PriceUpdate(); 75 | writer.write(priceUpdate); 76 | assertEquals(Structure.Data + Length.StatusFlag + Length.Metadata + RECORD_SIZE , mem.getLongVolatile(Structure.Limit)); 77 | 78 | writer.write(priceUpdate); 79 | assertEquals(Structure.Data + 2 * (Length.StatusFlag + Length.Metadata + RECORD_SIZE), mem.getLongVolatile(Structure.Limit)); 80 | } 81 | 82 | class PriceUpdate implements MappedBusMessage { 83 | 84 | public static final int TYPE = 0; 85 | 86 | private int source; 87 | 88 | private int price; 89 | 90 | private int quantity; 91 | 92 | public PriceUpdate() { 93 | } 94 | 95 | public PriceUpdate(int source, int price, int quantity) { 96 | this.source = source; 97 | this.price = price; 98 | this.quantity = quantity; 99 | } 100 | 101 | public int type() { 102 | return TYPE; 103 | } 104 | 105 | public int getSource() { 106 | return source; 107 | } 108 | 109 | public void setSource(int source) { 110 | this.source = source; 111 | } 112 | 113 | public int getPrice() { 114 | return price; 115 | } 116 | 117 | public void setPrice(int price) { 118 | this.price = price; 119 | } 120 | 121 | public int getQuantity() { 122 | return quantity; 123 | } 124 | 125 | public void setQuantity(int quantity) { 126 | this.quantity = quantity; 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | return "PriceUpdate [source=" + source + ", price=" + price + ", quantity=" + quantity + "]"; 132 | } 133 | 134 | public void write(MemoryMappedFile mem, long pos) { 135 | mem.putInt(pos, source); 136 | mem.putInt(pos + 4, price); 137 | mem.putInt(pos + 8, quantity); 138 | } 139 | 140 | public void read(MemoryMappedFile mem, long pos) { 141 | source = mem.getInt(pos); 142 | price = mem.getInt(pos + 4); 143 | quantity = mem.getInt(pos + 8); 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /test/io/mappedbus/MemoryMappedFileTest.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.io.File; 6 | 7 | import io.mappedbus.MemoryMappedFile; 8 | 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | public class MemoryMappedFileTest { 14 | 15 | public static final String FILE_NAME = "/tmp/memorymappedfile-test"; 16 | 17 | public static final long FILE_SIZE = 1000L; 18 | 19 | @Before public void before() { 20 | new File(FILE_NAME).delete(); 21 | } 22 | 23 | @After public void after() { 24 | new File(FILE_NAME).delete(); 25 | } 26 | 27 | @Test public void testVolatility() { 28 | final int LIMIT = 0; 29 | final int COMMIT = 8; 30 | final int DATA = 16; 31 | 32 | Thread writer = new Thread() { 33 | public void run() { 34 | try { 35 | MemoryMappedFile m = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 36 | Thread.sleep(500); 37 | m.putLongVolatile(LIMIT, 1); 38 | Thread.sleep(500); 39 | m.putLong(DATA, 2); 40 | m.putLongVolatile(COMMIT, 1); 41 | m.unmap(); 42 | } catch(Exception e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | }; 47 | writer.start(); 48 | 49 | try { 50 | MemoryMappedFile m = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 51 | long limit = m.getLong(LIMIT); 52 | assertEquals(0, limit); 53 | while (true) { 54 | limit = m.getLongVolatile(LIMIT); 55 | if (limit != 0) { 56 | assertEquals(1, limit); 57 | break; 58 | } 59 | } 60 | long commit = m.getLongVolatile(COMMIT); 61 | long data = m.getLong(DATA); 62 | assertEquals(0, commit); 63 | assertEquals(0, data); 64 | while (true) { 65 | commit = m.getLongVolatile(COMMIT); 66 | if (commit != 0) { 67 | assertEquals(1, commit); 68 | break; 69 | } 70 | } 71 | data = m.getLong(DATA); 72 | assertEquals(2, data); 73 | m.unmap(); 74 | writer.join(); 75 | } catch(Exception e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /test/io/mappedbus/MemoryTester.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Test program to ensure the ordering between non-volatile and volatile read/writes across 7 | * processes and CPUs. 8 | * 9 | * Writer: 10 | * - waits 3 seconds (for the reader to be started) 11 | * - writes a non-volatile value 12 | * - writes a volatile value 13 | * 14 | * Reader: 15 | * - reads a non-volatile value (should be zero at this point) 16 | * - reads a volatile value (should be zero at this point) 17 | * - in loop reads the volatile value and breaks the loop when the value is no longer zero 18 | * - reads the non-volatile value and makes sure it's updated 19 | * 20 | * To run: 21 | * in one terminal: 22 | * > taskset -c 0 java io.mappedbus.MemoryTester writer 23 | * 24 | * in another terminal: 25 | * > taskset -c 1 java io.mappedbus.MemoryTester reader 26 | * .. 27 | * Non Volatile Field was 0 as expected. 28 | * Volatile Field was 0 as expected. 29 | * Non Volatile Field was 1 as expected. 30 | * Volatile Field was 2 as expected. 31 | * 32 | */ 33 | public class MemoryTester { 34 | 35 | public static long NON_VOLATILE_FIELD = 0; 36 | 37 | public static long VOLATILE_FIELD = 512; 38 | 39 | public static final String FILE_NAME = "/tmp/memory-tester"; 40 | 41 | public static final long FILE_SIZE = 1000L; 42 | 43 | public static void main(String[] args) throws Exception { 44 | if (args.length == 1 && args[0].equals("reader")) { 45 | new MemoryTester().runReader(); 46 | } else if (args.length == 1 && args[0].equals("writer")) { 47 | new MemoryTester().runWriter(); 48 | } else { 49 | System.out.println("reader|writer"); 50 | } 51 | } 52 | 53 | public void runWriter() throws Exception { 54 | new File(FILE_NAME).delete(); 55 | System.out.println("Waiting 3s, now start the reader"); 56 | Thread.sleep(3000); 57 | MemoryMappedFile mem = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 58 | mem.putLong(NON_VOLATILE_FIELD, 1); 59 | mem.putLongVolatile(VOLATILE_FIELD, 2); 60 | mem.unmap(); 61 | } 62 | 63 | public void runReader() throws Exception { 64 | MemoryMappedFile mem = new MemoryMappedFile(FILE_NAME, FILE_SIZE); 65 | long nonVolatileField = mem.getLong(NON_VOLATILE_FIELD); 66 | assertEquals(0, nonVolatileField, "Non Volatile Field"); 67 | long volatileField = mem.getLongVolatile(VOLATILE_FIELD); 68 | assertEquals(0, volatileField, "Volatile Field"); 69 | long start = System.currentTimeMillis(); 70 | while (true) { 71 | volatileField = mem.getLongVolatile(VOLATILE_FIELD); 72 | if (volatileField != 0) { 73 | break; 74 | } 75 | if (System.currentTimeMillis() - start > 3000) { 76 | System.out.println("TEST FAILED: timeout waiting for the volatile field to change"); 77 | return; 78 | } 79 | } 80 | nonVolatileField = mem.getLong(NON_VOLATILE_FIELD); 81 | assertEquals(1, nonVolatileField, "Non Volatile Field"); 82 | assertEquals(2, volatileField, "Volatile Field"); 83 | mem.unmap(); 84 | } 85 | 86 | public void assertEquals(long expected, long actual, String name) { 87 | if(expected == actual) { 88 | System.out.println(name + " was " + actual + " as expected."); 89 | } else { 90 | System.out.println("TEST FAILED: " + name + " was " + actual + ", but expected " + expected + "."); 91 | System.exit(0); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /test/io/mappedbus/ObjectBasedIntegrityTest.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import io.mappedbus.MappedBusMessage; 5 | import io.mappedbus.MappedBusReader; 6 | import io.mappedbus.MappedBusWriter; 7 | import io.mappedbus.MemoryMappedFile; 8 | 9 | import java.io.File; 10 | 11 | import org.junit.After; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | /** 16 | * This class tests that records written by multiple concurrent writers are stored correctly. 17 | * 18 | * For more exhaustive testing NUM_RUNS can be increased. 19 | * 20 | */ 21 | public class ObjectBasedIntegrityTest { 22 | 23 | public static final String FILE_NAME = "/tmp/objectbased-integrity-test"; 24 | 25 | public static final long FILE_SIZE = 40000000; 26 | 27 | public static final int NUM_READERS = 8; 28 | 29 | public static final int NUM_WRITERS = 4; 30 | 31 | public static final int RECORD_LENGTH = 16; 32 | 33 | public static final int NUM_RECORDS_PER_WRITER = 300000; 34 | 35 | public static final int NUM_RECORDS = NUM_RECORDS_PER_WRITER * NUM_WRITERS; 36 | 37 | public static final int NUM_RUNS = 10; 38 | 39 | @Before public void before() { 40 | new File(FILE_NAME).delete(); 41 | } 42 | 43 | @After public void after() { 44 | new File(FILE_NAME).delete(); 45 | } 46 | 47 | @Test public void test() throws Exception { 48 | for (int i=0; i < NUM_RUNS; i++) { 49 | runTest(); 50 | } 51 | } 52 | 53 | private void runTest() throws Exception { 54 | new File(FILE_NAME).delete(); 55 | 56 | Writer[] writers = new Writer[NUM_WRITERS]; 57 | for (int i = 0; i < writers.length; i++) { 58 | writers[i] = new Writer(i + 1); 59 | } 60 | for (int i = 0; i < writers.length; i++) { 61 | writers[i].start(); 62 | } 63 | Reader[] readers = new Reader[NUM_READERS]; 64 | for (int i=0; i < readers.length; i++) { 65 | readers[i] = new Reader(); 66 | } 67 | for (int i=0; i < readers.length; i++) { 68 | readers[i].start(); 69 | } 70 | for (int i = 0; i < writers.length; i++) { 71 | writers[i].join(); 72 | } 73 | for (int i = 0; i < readers.length; i++) { 74 | readers[i].join(); 75 | } 76 | for (int i = 0; i < readers.length; i++) { 77 | assertEquals(false, readers[i].hasFailed()); 78 | assertEquals(NUM_RECORDS, readers[i].getRecordsReceived()); 79 | } 80 | } 81 | 82 | class Writer extends Thread { 83 | 84 | private final int id; 85 | 86 | public Writer(int id) { 87 | this.id = id; 88 | } 89 | 90 | public void run() { 91 | try { 92 | MappedBusWriter writer = new MappedBusWriter(ObjectBasedIntegrityTest.FILE_NAME, ObjectBasedIntegrityTest.FILE_SIZE, ObjectBasedIntegrityTest.RECORD_LENGTH); 93 | writer.open(); 94 | Record record = new Record(); 95 | record.setKey(id); 96 | long value = id; 97 | for (int i=0; i < ObjectBasedIntegrityTest.NUM_RECORDS_PER_WRITER; i++) { 98 | record.setKey(id); 99 | record.setValue(value); 100 | value += NUM_WRITERS; 101 | writer.write(record); 102 | } 103 | writer.close(); 104 | } catch(Exception e) { 105 | e.printStackTrace(); 106 | } 107 | } 108 | } 109 | 110 | class Reader extends Thread { 111 | 112 | private int recordsReceived; 113 | 114 | private boolean failed; 115 | 116 | public void run() { 117 | try { 118 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_LENGTH); 119 | reader.open(); 120 | long[] counters = new long[NUM_WRITERS]; 121 | for (int i=0; i < counters.length; i++) { 122 | counters[i] = i+1; 123 | } 124 | Record record = new Record(); 125 | while (true) { 126 | if (reader.next()) { 127 | int type = reader.readType(); 128 | if (type == Record.TYPE) { 129 | reader.readMessage(record); 130 | long key = record.getKey(); 131 | long value = record.getValue(); 132 | long expected = counters[(int)(key-1)]; 133 | if (expected != value) { 134 | System.out.println("Expected: " + counters[(int)(key-1)] + ", actual: " + value); 135 | failed = true; 136 | return; 137 | } 138 | counters[(int)(key-1)] += NUM_WRITERS; 139 | } 140 | recordsReceived++; 141 | if (recordsReceived >= NUM_RECORDS) { 142 | break; 143 | } 144 | } 145 | } 146 | reader.close(); 147 | } catch(Exception e) { 148 | e.printStackTrace(); 149 | } 150 | } 151 | 152 | public boolean hasFailed() { 153 | return failed; 154 | } 155 | 156 | public int getRecordsReceived() { 157 | return recordsReceived; 158 | } 159 | } 160 | 161 | class Record implements MappedBusMessage { 162 | 163 | public static final int TYPE = 0; 164 | 165 | private long key; 166 | 167 | private long value; 168 | 169 | public int type() { 170 | return TYPE; 171 | } 172 | 173 | public long getKey() { 174 | return key; 175 | } 176 | 177 | public void setKey(long key) { 178 | this.key = key; 179 | } 180 | 181 | public long getValue() { 182 | return value; 183 | } 184 | 185 | public void setValue(long value) { 186 | this.value = value; 187 | } 188 | 189 | public void write(MemoryMappedFile mem, long pos) { 190 | mem.putLong(pos, key); 191 | mem.putLong(pos + 8, value); 192 | } 193 | 194 | public void read(MemoryMappedFile mem, long pos) { 195 | key = mem.getLong(pos); 196 | value = mem.getLong(pos + 8); 197 | } 198 | } 199 | } -------------------------------------------------------------------------------- /test/io/mappedbus/RollbackTest.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.File; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | public class RollbackTest { 10 | 11 | public static final String FILE_NAME = "/tmp/rollback-tester"; 12 | 13 | public static final int RECORD_SIZE = 8 * 8; 14 | 15 | public static final int RECORDS = 20; 16 | 17 | public static final long FILE_SIZE = (RECORD_SIZE + MappedBusConstants.Length.RecordHeader) * RECORDS + MappedBusConstants.Length.Limit; 18 | 19 | public static final long INITIAL_VALUE = 1111111111111111111L; 20 | 21 | @Test 22 | public void test() { 23 | new File(FILE_NAME).delete(); 24 | 25 | Writer writer = new Writer(); 26 | Reader reader = new Reader(); 27 | 28 | writer.start(); 29 | reader.start(); 30 | 31 | try { 32 | writer.join(); 33 | reader.join(); 34 | } catch (InterruptedException e) { 35 | } 36 | } 37 | 38 | class Writer extends Thread { 39 | public void run() { 40 | try { 41 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_SIZE); 42 | reader.open(); 43 | 44 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 45 | writer.open(); 46 | 47 | Message message = new Message(); 48 | 49 | for (long i = 0; i < RECORDS; i++) { 50 | message.setValue(INITIAL_VALUE + i); 51 | if(i == 5 || i == 15) { 52 | long commitPos = writer.writeRecord(message); 53 | Thread.sleep(1000); 54 | boolean result = writer.commit(commitPos); 55 | assertEquals(false, result); 56 | } else { 57 | writer.write(message); 58 | } 59 | } 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | 66 | class Reader extends Thread { 67 | public void run() { 68 | try { 69 | MappedBusReader reader = new MappedBusReader(FILE_NAME, FILE_SIZE, RECORD_SIZE); 70 | reader.setTimeout(500); 71 | reader.open(); 72 | 73 | MappedBusWriter writer = new MappedBusWriter(FILE_NAME, FILE_SIZE, RECORD_SIZE); 74 | writer.open(); 75 | 76 | Message message = new Message(); 77 | 78 | for (int i = 0; i < RECORDS - 1; i++) { 79 | if(i == 5 || i == 15) { 80 | continue; 81 | } 82 | message.setValue(INITIAL_VALUE + i); 83 | while(true) { 84 | if (reader.next()) { 85 | reader.readMessage(message); 86 | break; 87 | } 88 | } 89 | } 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | } 95 | 96 | class Message implements MappedBusMessage { 97 | 98 | long value; 99 | 100 | public void setValue(long value) { 101 | this.value = value; 102 | } 103 | 104 | @Override 105 | public void write(MemoryMappedFile mem, long pos) { 106 | for(int i = 0; i < 8; i++) { 107 | mem.putLong(pos + i * 8, value); 108 | } 109 | } 110 | 111 | @Override 112 | public void read(MemoryMappedFile mem, long pos) { 113 | for (int i = 0; i < 8; i++) { 114 | long valueRead; 115 | if ((valueRead = mem.getLong(pos + i * 8)) != value) { 116 | throw new RuntimeException("Excepted: " + value + " but read " + valueRead); 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | public int type() { 123 | return 0; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /test/io/mappedbus/TokenTest.java: -------------------------------------------------------------------------------- 1 | package io.mappedbus; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import io.mappedbus.MappedBusMessage; 5 | import io.mappedbus.MappedBusReader; 6 | import io.mappedbus.MappedBusWriter; 7 | import io.mappedbus.MemoryMappedFile; 8 | 9 | import java.io.File; 10 | 11 | import org.junit.After; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | /** 16 | * This class tests that no messages are lost by passing a token around between a number of nodes. 17 | * 18 | * At the end there's a check that each node received the expected number of messages. 19 | * 20 | * For more exhaustive testing NUM_RUNS can be increased. 21 | * 22 | */ 23 | public class TokenTest { 24 | 25 | public static final String FILE_NAME = "/tmp/token-test"; 26 | 27 | public static final int RECORD_SIZE = 8; 28 | 29 | public static final long FILE_SIZE = 20000000L; 30 | 31 | public static final int NUM_MESSAGES = 100000; 32 | 33 | public static final int NUM_NODES = 3; 34 | 35 | public static final int WAIT_TIME = 10000; 36 | 37 | public static final int NUM_RUNS = 100; 38 | 39 | @Before public void before() { 40 | new File(FILE_NAME).delete(); 41 | } 42 | 43 | @After public void after() { 44 | new File(FILE_NAME).delete(); 45 | } 46 | 47 | @Test public void test() throws Exception { 48 | for (int i=0; i < NUM_RUNS; i++) { 49 | runTest(); 50 | } 51 | } 52 | 53 | private void runTest() throws Exception { 54 | new File(FILE_NAME).delete(); 55 | 56 | Node[] nodes = new Node[NUM_NODES]; 57 | 58 | for (int i = 0; i < nodes.length; i++) { 59 | nodes[i] = new Node(i, NUM_NODES, NUM_MESSAGES, FILE_NAME, FILE_SIZE, RECORD_SIZE); 60 | } 61 | 62 | for (int i = 0; i < nodes.length; i++) { 63 | nodes[i].start(); 64 | } 65 | 66 | try { 67 | for (int i = 0; i < nodes.length; i++) { 68 | nodes[i].join(WAIT_TIME); 69 | } 70 | } catch(Exception e) { 71 | e.printStackTrace(); 72 | } 73 | 74 | for (int i = 0; i < nodes.length; i++) { 75 | assertEquals(NUM_MESSAGES, nodes[i].getMessagesReceived()); 76 | } 77 | 78 | try { 79 | for (int i = 0; i < nodes.length; i++) { 80 | nodes[i].close(); 81 | } 82 | } catch(Exception e) { 83 | e.printStackTrace(); 84 | } 85 | } 86 | 87 | class Node extends Thread { 88 | 89 | private final int id; 90 | 91 | private final int numberOfNodes; 92 | 93 | private final int numberOfMessages; 94 | 95 | private int messagesReceived; 96 | 97 | private MappedBusReader reader; 98 | 99 | private MappedBusWriter writer; 100 | 101 | public Node(int id, int numberOfNodes, int numberOfMessages, String fileName, long fileSize, int recordSize) throws Exception { 102 | this.id = id; 103 | this.numberOfNodes = numberOfNodes; 104 | this.numberOfMessages = numberOfMessages; 105 | writer = new MappedBusWriter(fileName, fileSize, recordSize); 106 | writer.open(); 107 | reader = new MappedBusReader(fileName, fileSize, recordSize); 108 | reader.open(); 109 | } 110 | 111 | public void run() { 112 | try { 113 | 114 | Token token = new Token(); 115 | 116 | if (id == 0) { 117 | token.setFrom(id); 118 | token.setTo(1); 119 | writer.write(token); 120 | } 121 | 122 | while (true) { 123 | if (reader.next()) { 124 | //System.out.println(id + " Read: " + reader.readMessage(token)); 125 | reader.readMessage(token); 126 | messagesReceived++; 127 | if (messagesReceived >= numberOfMessages) { 128 | break; 129 | } 130 | 131 | if (token.getTo() == id) { 132 | //Thread.sleep(1000); 133 | token.setFrom(id); 134 | token.setTo((id + 1) % numberOfNodes); 135 | writer.write(token); 136 | } 137 | } 138 | } 139 | } catch(Exception e) { 140 | e.printStackTrace(); 141 | } 142 | } 143 | 144 | public void close() throws Exception { 145 | writer.close(); 146 | reader.close(); 147 | } 148 | 149 | public int getMessagesReceived() { 150 | return messagesReceived; 151 | } 152 | } 153 | 154 | class Token implements MappedBusMessage { 155 | 156 | public static final int TYPE = 0; 157 | 158 | private int from; 159 | 160 | private int to; 161 | 162 | public Token() { 163 | } 164 | 165 | public Token(int source, int target) { 166 | this.from = source; 167 | this.to = target; 168 | } 169 | 170 | public int type() { 171 | return TYPE; 172 | } 173 | 174 | public int getFrom() { 175 | return from; 176 | } 177 | 178 | public void setFrom(int from) { 179 | this.from = from; 180 | } 181 | 182 | public int getTo() { 183 | return to; 184 | } 185 | 186 | public void setTo(int to) { 187 | this.to = to; 188 | } 189 | 190 | @Override 191 | public String toString() { 192 | return "Token [from=" + from + ", to=" + to + "]"; 193 | } 194 | 195 | public void write(MemoryMappedFile mem, long pos) { 196 | mem.putInt(pos, from); 197 | mem.putInt(pos + 4, to); 198 | } 199 | 200 | public void read(MemoryMappedFile mem, long pos) { 201 | from = mem.getInt(pos); 202 | to = mem.getInt(pos + 4); 203 | } 204 | } 205 | } --------------------------------------------------------------------------------