├── LICENSE ├── README.md ├── docs ├── CNAME ├── _config.yml ├── _layouts │ ├── default.html │ └── post.html ├── _posts │ ├── 2020-03-15-hello-persistent-world.md │ ├── 2020-03-16-jep-352.md │ ├── 2020-03-17-using-jep-352-api.md │ ├── 2020-10-29-narayana.md │ ├── 2020-11-05-infinispan.md │ ├── 2021-01-11-new-year.md │ ├── 2021-04-06-narayana-config.md │ ├── 2021-04-12-debug-logging.md │ ├── 2021-04-14-infinispan-config.md │ ├── 2021-04-16-pmem-in-containers.md │ ├── 2021-06-15-the-lts-cometh.md │ ├── 2021-07-12-testing-java-pmem.md │ ├── 2021-08-03-beta2.md │ └── 2021-09-15-ga-release.md ├── blog │ ├── atom.xml │ └── index.html ├── css │ └── main.css └── index.html ├── logwriting-benchmark ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── mashona │ └── logwriting │ └── perftest │ ├── AppendOnlyLogBenchmark.java │ ├── ArrayStoreBenchmark.java │ ├── MappedFileChannelBenchmark.java │ └── SimpleHardwareBenchmark.java ├── logwriting ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── mashona │ │ └── logwriting │ │ ├── AppendOnlyLog.java │ │ ├── AppendOnlyLogImpl.java │ │ ├── AppendOnlyLogImplConfig.java │ │ ├── AppendOnlyLogWithLocation.java │ │ ├── ArrayStore.java │ │ ├── ArrayStoreImpl.java │ │ ├── MappedFileChannel.java │ │ ├── MappedFileChannelMetadata.java │ │ ├── PersistenceHandle.java │ │ └── PmemUtil.java │ └── test │ ├── java │ └── io │ │ └── mashona │ │ └── logwriting │ │ ├── AppendOnlyLogTests.java │ │ ├── ArrayStoreTests.java │ │ ├── BMObjectAnnotationHandler.java │ │ ├── DummyExtensionContext.java │ │ ├── ExecutionTracer.java │ │ ├── MappedFileChannelMetadataTests.java │ │ ├── MappedFileChannelTests.java │ │ ├── PersistenceHandleTests.java │ │ ├── PmemUtilTests.java │ │ └── WithBytemanFrom.java │ └── resources │ └── logback.xml └── pobj ├── README.md ├── pom.xml └── src ├── main └── java │ └── io │ └── mashona │ └── pobj │ ├── PersistenceProvider.java │ ├── allocator │ ├── CompositeAllocator.java │ ├── CompositeAllocatorPersistence.java │ ├── MemoryHeap.java │ ├── RegionBitmap.java │ ├── RegionBitmapPersistence.java │ └── RegionConfig.java │ ├── runtime │ ├── MemoryBackedObject.java │ └── MemoryOperations.java │ └── transaction │ ├── PersistentTransaction.java │ ├── PersistentTransactionManager.java │ ├── TransactionManager.java │ ├── TransactionStore.java │ ├── TransactionalCompositeAllocator.java │ ├── TransactionalMemoryHeap.java │ ├── TransactionalMemoryOperations.java │ ├── VolatileTransaction.java │ └── events │ ├── BeforeWriteEvent.java │ ├── BeforeWriteEventPersistence.java │ ├── CreateEvent.java │ ├── DeallocateEvent.java │ ├── DeallocateEventPersistence.java │ ├── DeleteEvent.java │ ├── MallocEvent.java │ ├── MallocEventPersistence.java │ ├── OutcomeEvent.java │ ├── OutcomeEventPersistence.java │ ├── TransactionEvent.java │ └── TransactionEventPersistence.java └── test └── java └── io └── mashona └── pobj ├── allocator ├── BogusMemoryBackedObject.java ├── CompositeAllocatorPersistenceTests.java ├── CompositeAllocatorTests.java ├── LargeMemoryBackedObject.java ├── MemoryHeapTests.java ├── RegionBitmapPersistenceTests.java └── RegionBitmapTests.java ├── generated ├── MBOTestEntity.java ├── MemoryOperationsTests.java └── PointImpl.java └── transaction ├── CrashRecoveryTests.java ├── PersistentTransactionWiringTests.java ├── TransactionWiringTests.java └── events ├── TransactionEventPersistenceTests.java └── TransactionEventTests.java /docs/CNAME: -------------------------------------------------------------------------------- 1 | mashona.io -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | name: Mashona, Persistent Memory library for pure Java 2 | markdown: kramdown 3 | permalink: /blog/:year/:month/:day/:title 4 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page.title }} 5 | 6 | 7 | 8 | 14 |
15 | {{ content }} 16 |
17 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |

{{ page.title }}

5 |

{{ page.date | date_to_string }}

6 | 7 |
8 | {{ content }} 9 |
10 | -------------------------------------------------------------------------------- /docs/_posts/2020-03-15-hello-persistent-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Hello, Persistent World!" 4 | date: 2020-03-15 5 | --- 6 | 7 | This month's launch of [JDK 14](http://jdk.java.net/14/) marks the general availability of native Persistent Memory support for JVM users. 8 | Although a significant foundational milestone, it's not the opening chapter in the story of this new approach to programming. In this first Mashona project blog, we look at some parts of the backstory. 9 | 10 | Persistent Memory hardware has been around for some years, often in the form of DIMMs combining DRAM, Flash memory and capacitors to provide fault (i.e. power loss) tolerance by copying the contents of the volatile DRAM to the persistent Flash using power from the capacitors in the case of an unexpected loss of power to the motherbaord. 11 | 12 | This style of Persistent Memory, known as NVDIMM-N, benefits from the performance characteristics of DRAM, but at the cost of being limited by DRAM's relatively small size. 13 | 14 | More recently, Intel introduced Optane branded DCPMMs. These use the same 3D XPoint chips as Optane SSDs for persistent storage, but interface to the platform memory controller rather than the storage bus. They offer larger capacities than DRAM whilst still having better performance than Flash. 15 | 16 | Alongside the new DCPMMs (like DIMMs, but not strictly spec compatible), the Intel platform has processor instructions (CLFLUSH, CLFLUSHOPT and CLWB) for managing flushing volatile cache lines to the non-volatile memory controller. 17 | 18 | Careful use of these instructions in conjunction with memory fences is at the core of Persistent Memory programming techniques. 19 | As the instructions are non-privileged, it's even possible to use new O/S features to bypass the kernel when doing memory mapped I/O to files backed by Persistent Memory. 20 | With the storage bus and the kernel out of the picture, user code can exploit the full performance of the new hardware to perform persistence operations with unprecedented speed. 21 | 22 | Here lies the problem for programmers working in managed languages on the JVM (Java, Scala, Kotlin, Clojure, ...), for the nature of the managed runtime affords no way to inline assembly code or macros as is possible with C/C++ 23 | 24 | JNI provides a loophole, allowing function calls out to processor native libraries built on the low level cache management instructions. 25 | But, whilst this approach is functional, it's not fast. JNI plumbing adds considerable overhead to small function calls, preventing programs from exploiting the full performance capability of the hardware. 26 | 27 | For traditional disk I/O, this can reasonably be overlooked. Even with SSD, the hardware path cost still dominates any inefficiencies in the software path, to the extent that there is no great benefit from optimizing out the kernel or JNI calls. 28 | 29 | With Persistent Memory, programs seeking the best possible I/O paths must look for ways to reduce these software costs, as they are now a more significant part of the total overhead. 30 | 31 | So we come to the point in the story where OpenJDK engineering makes its entrance. For the JVM to be a competitive platform on which to run applications that take advantage of Persistent Memory, it needs to provide built-in support for efficient calls to the cache management instructions. 32 | 33 | In the [next blog](https://jhalliday.github.io/mashona/blog/2020/03/16/jep-352), we look at the API and implementation of OpenJDK 14's new support for Persistent Memory, 34 | [JEP 352: Non-Volatile Mapped Byte Buffers](https://openjdk.java.net/jeps/352) 35 | -------------------------------------------------------------------------------- /docs/_posts/2020-03-16-jep-352.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "New in JDK 14: Persistent Memory support" 4 | date: 2020-03-16 5 | --- 6 | 7 | In the [last blog](https://jhalliday.github.io/mashona/blog/2020/03/15/hello-persistent-world) we looked at the background to Persistent Memory, including new hardware and new processor instructions. 8 | 9 | In this post we look at how the JDK has evolved to offer a strong platform for accessing Persistent Memory in a performant manner. 10 | 11 | Memory-mapped I/O has long provided an efficient way to access a file as a region of memory. 12 | In the Java platform APIs, it takes the form of [```MappedByteBuffer```](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/MappedByteBuffer.html), a class that provides a [```ByteBuffer```](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/ByteBuffer.html) style overlay onto a range of memory that the operating system wires to a file. 13 | 14 | The region of memory provided by the O/S when we create a mapping of a file is normally a volatile (DRAM) copy of the file contents. 15 | To guarantee persistence, it's necessary to call the [```force()```](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/MappedByteBuffer.html#force()) method, which the JVM implements as a [```msync()```](http://man7.org/linux/man-pages/man2/msync.2.html) system call. 16 | 17 | msync() causes the kernel to flush the volatile in-memory copy back to the persistent storage device on which the file resides. Control returns to the Java code when the write is guaranteed to be complete, making it easy for the programmer to reason about persistence boundaries and timing in their code. 18 | 19 | With filesystems backed by Persistent Memory, this situation can be improved. Here the underlying storage device interface is itself byte-oriented rather than block-oriented. 20 | It's no longer necessary to make a DRAM copy of part of the persistent data in order to access it in a byte-oriented fashion. 21 | 22 | Instead, the O/S can map memory address space directly onto the actual persistent device, bypassing the block cache. 23 | This mapping mode, known as DAX, incurs system call cost only when the mapping is created. Thereafter, system calls can be elided. 24 | 25 | In place of expensive ```msync()``` system calls, the application can instead use processor cache line management instructions (CLFLUSH, CLFUSHOPT and CLWB) in conjunction with memory fence operations to manage persistence boundaries. 26 | 27 | Unfortunately, until now there was no way to do this from code running in the JVM. JNI calls could be made to functions wrapping the processor instructions, but the JVM itself was unaware of them. 28 | 29 | Starting with JDK 14, this situation has changed. Not only can Java programmers now create MappedByteBuffers in direct (DAX) mode, they can also ```force()``` them efficiently without resorting to JNI. 30 | The JVM will now detect the DAX mapping and automatically optimize out the ```msync()``` system call when ```force()``` is invoked, replacing it with faster user space memory management instructions. 31 | 32 | Using this feature, contributed to OpenJDK by Red Hat's Java team, applications running on the JVM can access Persistent Memory as efficiently as platform native C/C++ applications can. 33 | 34 | In the [next blog](https://jhalliday.github.io/mashona/blog/2020/03/17/using-jep-352-api) we look at the Java platform library API changes in detail and show how to create a program that uses them to efficiently write file changes to Persistent Memory from Java. -------------------------------------------------------------------------------- /docs/_posts/2020-03-17-using-jep-352-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Using Non-Volatile Mapped Byte Buffers in JDK 14" 4 | date: 2020-03-17 5 | --- 6 | 7 | In the [last blog](https://jhalliday.github.io/mashona/blog/2020/03/16/jep-352) we looked at how Red Hat's engineers have lead the work evolving OpenJDK to provide efficient access to Persistent Memory. 8 | 9 | In this post we get hands-on, showing how to use the new API to write code with JDK 14 10 | 11 | A [```MappedByteBuffer```](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/MappedByteBuffer.html) provides a way to access a region of memory, corresponding to a file, from Java. 12 | 13 | The steps to creating a ```MappedByteBuffer``` for Persistent Memory are almost the same as in the non-persistent case. First, create a ```FileChannel```: 14 | 15 | ```java 16 | final File file = new File("/path/to/some/file"); 17 | 18 | final FileChannel fileChannel = (FileChannel) Files 19 | .newByteChannel(file.toPath(), EnumSet.of( 20 | StandardOpenOption.READ, 21 | StandardOpenOption.WRITE, 22 | StandardOpenOption.CREATE)); 23 | 24 | MappedByteBuffer mappedByteBuffer = null; 25 | ``` 26 | 27 | Next, at the point the memory map is instantiated, use the new ```ExtendedMapMode``` flag to take advantage of DAX mode mapping: 28 | 29 | ```java 30 | try { 31 | mappedByteBuffer = fileChannel.map(ExtendedMapMode.READ_WRITE_SYNC, position, size); 32 | } catch(IOException e) { 33 | // mapping failed! maybe retry with MapMode.READ_WRITE instead? 34 | } 35 | ``` 36 | 37 | Note that the DAX mode mapping will fail with ```IOException: Operation not supported``` if the underlying file is not on a DAX enabled filesystem. If that happens, you can simply fallback to a regular mapping. 38 | 39 | That's it! No further code changes are required to enable use of the optimizations. 40 | 41 | The newly created MappedByteBuffer instance will internally carry a flag indicating that it is DAX enabled. 42 | To guarantee persistence of changes to the buffer, just call ```mappedByteBuffer.force()``` as normal. 43 | 44 | The new JDK library implementation of ```force()``` will consult the flag and use performance optimized cache management instructions if it can, or fallback to the old behaviour of calling ```msync()``` if it can't. 45 | 46 | But wait, there is more! 47 | 48 | There is one further optional change you can make to improve write performance still further. 49 | The ```MappedByteBuffer``` API has also been updated to offer a new [```force(int index, int length)```](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/nio/MappedByteBuffer.html#force(int,int)) variant. 50 | 51 | For the non-optimized case this provides little gain, as the O/S already tracks dirty blocks and flushes only those that have been modified when ```force()``` calls ```msync()``` 52 | 53 | For the optimized case however, there is no dirty tracking in software, so the JDK will issue cache writeback instructions for each cache line in the entire range. 54 | 55 | Although the cache hardware can optimize out the writebacks for non-dirty or non-loaded lines, it's still an overhead to do so. Better not to issue the instructions in the first place. 56 | Hence the finer grained version of ```force()``` that allows a range to be provided explicitly. 57 | 58 | The optimizations are particularly powerful for applications that require small log append operations or other modifications of sub-block size, as the I/O is now at cache line (64 byte) granularity rather than block (4K) granularity. 59 | 60 | -------------------------------------------------------------------------------- /docs/_posts/2020-11-05-infinispan.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Infinispan persistence performance tests" 4 | date: 2020-11-05 5 | --- 6 | 7 | The pre-release performance testing and tuning of the mashona [logwriting](https://github.com/jhalliday/mashona/tree/main/logwriting) module continues with a look at how it accelerates the Infinispan data grid. 8 | 9 | ## Background 10 | 11 | [Infinispan](https://infinispan.org/) is a distributed in-memory key/value data store written in Java. 12 | For these tests we're running it as an embedded library, though it's also accessible remotely with a variety of language-independent network protocols. 13 | 14 | Infinispan can be used as a cache, but does much more than that. 15 | When configured with a persistent store, it is a nosql database. 16 | 17 | Whilst reads can be served from memory, writes to a persistent data grid node must be written to disk to ensure recovery in the event of failures. 18 | This can can be a significant bottleneck when the data is changing rapidly, especially where updates must be totally reliable, as this requirement means frequent flushes of modified data from O/S cache to persistent storage devices. 19 | 20 | As with the similar [requirement in Narayana](https://jhalliday.github.io/mashona/blog/2020/10/29/narayana), this looks like a good use case for persistent memory, so let's see what we can do to relieve the persistence bottleneck. 21 | 22 | ## Persistent store integration 23 | 24 | Infinispan has several [persistent store options](https://infinispan.org/docs/stable/titles/configuring/configuring.html#cache_store_implementations). 25 | For these experiments we're using the Soft-Index File Store (SIFS), a disk based variant which holds an in-memory index to on-disk values, written using sequential log writes. 26 | 27 | The SIFS implementation internally uses [FileChannel](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/channels/FileChannel.html) as its API to the persistent data files, 28 | employing a `FileChannel.force()` call to invoke the fsync syscall to ensure persistence. 29 | This makes mashona's `MappedFileChannel` a good fit for the integration. 30 | As `MappedFileChannel extends FileChannel`, only the two points in the code where the channel is opened need to be modified. The other methods in the persistence code need not be aware if they are running on persistent memory or conventional storage devices. 31 | 32 | ## Test results 33 | 34 | For these benchmarks, we use an embedded single-node cache configured with Soft-Index File Store persistence in immediate mode i.e. with `syncWrites(true)`. 35 | We place the persistence store on a variety of devices and measure the update rate that can be achieved. 36 | 37 | ``` 38 | threads FlashSSD OptaneSSD 39 | 1 3880 5499 40 | 4 11963 15587 41 | 16 28299 73288 42 | ``` 43 | 44 | As with the Narayana tests, the system is using an Intel [D3-S4610](https://www.intel.co.uk/content/www/uk/en/products/memory-storage/solid-state-drives/data-center-ssds/d3-series/d3-s4610-series/d3-s4610-480gb-2-5inch-3d2.html) flash SSD against an Intel [DC P4800X](https://www.intel.co.uk/content/www/uk/en/products/memory-storage/solid-state-drives/data-center-ssds/optane-dc-ssd-series/optane-dc-p4800x-series/p4800x-750gb-2-5-inch.html) Optane SSD. 45 | The 3D-XPoint technology gives a clear advantage, but it's limited by the complexity of the path between the CPU and the drive. 46 | 47 | Let's try putting the stores on a single Optane DC PMM module, with the file system in sector mode. 48 | This configuration offers the same filesystem block level persistence guarantees as regular drives, but moves the storage from the I/O path to the memory bus. 49 | 50 | ``` 51 | threads FlashSSD OptaneSSD OptaneDCPMM 52 | 1 3880 5499 7010 53 | 4 11963 15587 17710 54 | 16 28299 73288 68696 55 | ``` 56 | 57 | Here we see a substantial advantage at first due to the lower latency, but as the load increases the greater buffering and internal striping of the drive gives it an advantage. 58 | To keep the DC PMM option ahead we'd need to interlace (i.e. stripe) multiple modules, as the drive does internally. 59 | 60 | So far we're still going via the kernel's fsync syscall to flush O/S caches and guarantee persistence. 61 | However, much of the advantage of persistent memory comes from DAX mode, which is not available to disk drives. 62 | 63 | Using DAX mode in conjunction with the [JEP-352](https://jhalliday.github.io/mashona/blog/2020/03/16/jep-352) changes added in JDK 14, we can flush direct from user space without the cost of a system call. 64 | On storage as fast as the Optane modules the syscall cost is significant, so eliminating it from the path should help a lot. 65 | This is what mashona's MappedFileChannel is designed to do. 66 | 67 | ``` 68 | threads FlashSSD mashona mult 69 | 1 3880 16355 4.2x 70 | 4 11963 50991 4.3x 71 | 16 28299 162930 5.8x 72 | ``` 73 | 74 | This approach utilises much more of the hardware's potential! 75 | 76 | The legacy assumptions of the store design still result in append-only sequential writes that don't take full advantage of the hardware parallelism. 77 | Even so, it's a big improvement for very little integration work. 78 | A store designed specifically for persistent memory could likely achieve even better results, but at the cost of much greater engineering effort. 79 | 80 | ## Conclusion 81 | 82 | With minimal code change to integrate the mashona logwriting library into Infinispan, 83 | we can substantially relieve the persistence bottleneck and allow the data grid to support use cases that were previously beyond reach. 84 | 85 | The **6x** improvement in performance over flash SSD for this use case, whilst very good, is more modest than the **10x** [seen](https://jhalliday.github.io/mashona/blog/2020/10/29/narayana) with the Narayana use case. 86 | A tailored design with more extensive code changes has the potential to raise performance still further. 87 | 88 | Even so, Infinispan, and by extension the Wildfly application server and other projects that use it, 89 | are well-prepared to make use of the new hardware, thanks to our JDK contributions and the mashona library. -------------------------------------------------------------------------------- /docs/_posts/2021-01-11-new-year.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "New Year, New Release" 4 | date: 2021-01-11 5 | --- 6 | 7 | The start of 2021 marks a new phase for the project, with the first Beta release out now. 8 | 9 | The 1.0 Beta1 release of the [logwriting](https://github.com/jhalliday/mashona/tree/main/logwriting) module is now ready for integration with JVM based applications seeking to benefit from the fast write performance of persistent memory. 10 | 11 | Providing a variety of binary logging APIs for different use cases, including a drop-in replacement for Java's FileChannel, the open source library already shows some impressive benchmarks numbers for use cases in 12 | [transactions](https://jhalliday.github.io/mashona/blog/2020/10/29/narayana) 13 | and 14 | [data grid](https://jhalliday.github.io/mashona/blog/2020/11/05/infinispan) 15 | use cases. 16 | 17 | We're sure the community will find many more applications that can benefit and we're looking forward to feedback from the early adopters. 2021 should be an interesting year! 18 | -------------------------------------------------------------------------------- /docs/_posts/2021-04-06-narayana-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Narayana Transaction Log, now on Persistent Memory" 4 | date: 2021-04-06 5 | --- 6 | 7 | With the recent release of [Narayana 5.11.0.Final](https://jbossts.blogspot.com/2021/03/narayana-5110final-released.html) 8 | it's now possible to have [fast](https://jhalliday.github.io/mashona/blog/2020/10/29/narayana) transaction logging to persistent memory right out of the box. 9 | 10 | The new SlotStore code is bundled in the release, making Narayana the first Java transaction system to have pure Java support for Persistent Memory. 11 | It can be enabled easily through Narayana's flexible configuration mechanisms. 12 | You can use the API, set System properties in Java, or pass them in from the command line. 13 | 14 | First, add the mashona pmem library to your classpath. 15 | 16 | ```xml 17 | 18 | 19 | io.mashona 20 | mashona-logwriting 21 | 1.0.0.Beta1 22 | 23 | ``` 24 | 25 | Then, choose the configuration style you prefer 26 | 27 | Option One, Configuration with the API: 28 | ```java 29 | import com.arjuna.ats.arjuna.common.CoreEnvironmentBean; 30 | import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean; 31 | import com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreAdaptor; 32 | import com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreEnvironmentBean; 33 | import com.arjuna.common.internal.util.propertyservice.BeanPopulator; 34 | ... 35 | 36 | // first, configure the SlotStore subsystem to use Persistent Memory 37 | SlotStoreEnvironmentBean config = BeanPopulator.getDefaultInstance(SlotStoreEnvironmentBean.class); 38 | config.setBackingSlotsClassName("com.arjuna.ats.internal.arjuna.objectstore.slot.PmemSlots"); 39 | // use a directory on a dax aware filesystem: 40 | config.setStoreDir("/mnt/pmem/dir"); 41 | // finally, tell the transaction log store to use the slot configuration 42 | BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class).setObjectStoreType(SlotStoreAdaptor.class.getName()); 43 | ``` 44 | 45 | Option Two, Java configuration with system properties. This avoids importing Narayana implementation classes: 46 | 47 | ```java 48 | System.setProperty("SlotStoreEnvironmentBean.backingSlotsClassName", 49 | "com.arjuna.ats.internal.arjuna.objectstore.slot.PmemSlots"); 50 | System.setProperty("SlotStoreEnvironmentBean.storeDir", 51 | "/mnt/pmem/test"); 52 | System.setProperty("ObjectStoreEnvironmentBean.objectStoreType", 53 | "com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreAdaptor"); 54 | ``` 55 | 56 | Option Three, if you prefer to keep the configuration outside the code: 57 | 58 | ```bash 59 | java -DSlotStoreEnvironmentBean.backingSlotsClassName=com.arjuna.ats.internal.arjuna.objectstore.slot.PmemSlots \ 60 | -DSlotStoreEnvironmentBean.storeDir=/mnt/pmem/test \ 61 | -DObjectStoreEnvironmentBean.objectStoreType=com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreAdaptor 62 | ``` 63 | 64 | That's it! There are additional configuration properties for tuning the space usage and behavior of the store, but the defaults are enough to get going with. 65 | 66 | In the unlikely event of problems, it may help to enable more detailed (i.e. debug/trace) output. 67 | Note that whilst the Narayana SlotStore system uses "com.arjuna.ats.arjuna" package logger, 68 | the mashona pmem library uses "io.mashona.logwriting". 69 | Setting one or both of these to a more detailed log level may help gather more information to diagnose any problems. 70 | -------------------------------------------------------------------------------- /docs/_posts/2021-04-12-debug-logging.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Debug and trace logging in Mashona" 4 | date: 2021-04-12 5 | --- 6 | 7 | If you would like to see what Mashona is doing internally at runtime, or you need to supply logs to a support provider for diagnostic purposes, then this post is for you. 8 | 9 | The Mashona logwriting library uses the [JBoss Logging](https://github.com/jboss-logging) library for its diagnostic logging statements. 10 | 11 | ## Embedded use 12 | 13 | If you're running it embedded in another JBoss project, such as [Narayana](https://narayana.io/) or [Infinispan](https://infinispan.org/), then you only need to use the project's existing logging configuration mechanism to enable the desired log level (e.g. DEBUG or TRACE) for the "io.mashona.logwriting" logger. 14 | 15 | ## Custom project setup 16 | 17 | If you're running it in your own project, a little more setup may be required. 18 | 19 | JBoss Logging is a dependency of Mashona and maven will pull it in automatically, or you can explicitly add it to your project 20 | 21 | ```xml 22 | 23 | org.jboss.logging 24 | jboss-logging 25 | 26 | 3.4.1.Final 27 | 28 | ``` 29 | 30 | JBoss Logging can use a number of backend logging systems, including [Apache Log4j](https://logging.apache.org/log4j/2.x/) and [Slf4j](http://www.slf4j.org/), as well as the JDK's built-in java.util.logging. 31 | Whilst you can select one explicitly (with the 'org.jboss.logging.provider' system property set to jboss | log4j2 | slf4j | jdk) it's usually easier just to let it find one itself by searching along the classpath. 32 | 33 | ### Option A: Using Log4j 34 | 35 | Add the library to your classpath if you don't already have it 36 | 37 | ```xml 38 | 39 | org.apache.logging.log4j 40 | log4j-core 41 | 42 | 2.14.1 43 | 44 | ``` 45 | 46 | Then add a **log4j2.xml** config file to the classpath or use one of the other [configuration mechanisms](https://logging.apache.org/log4j/2.x/manual/configuration.html). 47 | 48 | 49 | ### Option B: Using Slf4j 50 | 51 | Add the API and the [logback](http://logback.qos.ch) backend to the project if you don't already have them 52 | 53 | ```xml 54 | 55 | org.slf4j 56 | slf4j-api 57 | 58 | 1.7.30 59 | 60 | 61 | ch.qos.logback 62 | logback-classic 63 | 64 | 1.2.3 65 | 66 | ``` 67 | 68 | Then add a **logback.xml** config file to the classpath or use one of the other [configuration mechanisms](http://logback.qos.ch/manual/configuration.html). 69 | 70 | ### Option C: Using JBoss LogManager 71 | 72 | JBoss logging has its own backend, which you can add to you project with 73 | 74 | ```xml 75 | 76 | org.jboss.logmanager 77 | jboss-logmanager 78 | 79 | 2.1.18.Final 80 | 81 | ``` 82 | 83 | Then enable it to replace the default log manager by setting the system property 84 | 85 | `java.util.logging.manager=org.jboss.logmanager.LogManager` 86 | 87 | This LogManager has the same configuration format as Java's native logging system, 88 | so you need a **logging.properties** file on the classpath. 89 | The JBoss LogManager comes with its own Handlers and Formatters though, 90 | so your logging.properties file content may differ from the normal JDK one. 91 | For example 92 | ```properties 93 | logger.handlers=CONSOLE 94 | 95 | handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler 96 | handler.CONSOLE.level=INFO 97 | handler.CONSOLE.formatter=PATTERN 98 | 99 | formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter 100 | formatter.PATTERN.properties=pattern 101 | formatter.PATTERN.pattern=%d{HH:mm:ss.SSS} [%t] %-5p %c{36} %M:%L - %msg%n 102 | ``` 103 | 104 | ### Option D: Using JDK logging 105 | 106 | JBoss logging will delegate to Java's built-in logging framework by default, if it can't find an alternative logging library to use. 107 | Note that, unlike the other options, the JDK won't look for its config on the classpath. 108 | Configure the location of **logging.properties** explicitly with the system property 109 | 110 | `java.util.logging.config.file=/path/to/logging.properties` 111 | 112 | ## Logging pattern configuration 113 | 114 | For DEBUG and TRACE level logging, it's helpful to have thread (%t) and code location (%M %L) information in the logs. 115 | 116 | The recommended setting for log4j's [PatternLayout](https://logging.apache.org/log4j/2.x/manual/layouts.html) 117 | and slf4j-logback's [PatternLayout](http://logback.qos.ch/manual/layouts.html) is 118 | 119 | `%date{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} %method:%line - %msg%n` 120 | 121 | Note that JBoss LogManager uses the same fields, but refers to them only by their 122 | [single character names](https://github.com/jboss-logging/jboss-logmanager/blob/master/core/src/main/java/org/jboss/logmanager/formatters/FormatStringParser.java), 123 | so for it the equivalent pattern must be written as 124 | 125 | `%d{yyyy-MM-dd_HH:mm:ss.SSS} [%t] %-5p %c{36} %M:%L - %msg%n` 126 | 127 | For JDK native logging, the [format](https://docs.oracle.com/en/java/javase/11/docs/api/java.logging/java/util/logging/SimpleFormatter.html#format(java.util.logging.LogRecord)) is different again. 128 | 129 | `java.util.logging.SimpleFormatter.format=%1$s %4$s %2$s %5s%n` 130 | 131 | ## DEBUG or TRACE ? 132 | 133 | Mashona has little DEBUG level logging, but lots of TRACE level logging. 134 | In general, the entry and exit (return or throws) for every public method in the API has TRACE statements, 135 | designed to be read in conjunction with the logger's method and line fields. 136 | Whilst such TRACE logs can be large, this configuration is usually required if you're seeking support or filing issues against the library. 137 | -------------------------------------------------------------------------------- /docs/_posts/2021-04-14-infinispan-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Infinispan Data Store, now on Persistent Memory" 4 | date: 2021-04-14 5 | --- 6 | 7 | With the recent release of [Infinispan 12.1](https://infinispan.org/blog/2021/04/07/infinispan-12-1-0-final) 8 | it's now possible to have [fast](https://jhalliday.github.io/mashona/blog/2020/11/05/infinispan) data storage on persistent memory with just a few simple setup steps. 9 | 10 | The release incorporates some enhancements to Infinispan's Single Index File Store (SIFS), including a non-blocking model and support for persistent memory. 11 | 12 | ## Configuration file 13 | 14 | The persistent memory cache [configuration](https://infinispan.org/docs/stable/titles/configuring/configuring.html#sifs_cache_store) looks as it does for use on regular storage devices, such as SSD. 15 | Just add an appropriate `local-cache` to your `cache-container` 16 | 17 | ```xml 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | To enable the persistent memory code, two additional steps are necessary. 29 | 30 | ## Library installation 31 | 32 | First, add the mashona library to the server classpath 33 | 34 | ```bash 35 | $ cd lib 36 | # version latest at time of writing. Check for updates! 37 | $ wget https://repo1.maven.org/maven2/io/mashona/mashona-logwriting/1.0.0.Beta1/mashona-logwriting-1.0.0.Beta1.jar 38 | ``` 39 | 40 | Without the pmem library installed in the classpath, the system will default to normal usage: 41 | 42 | ```log 43 | server.log:2021-04-14 08:14:37,156 DEBUG (main) [org.infinispan.persistence.sifs.FileProvider] Persistent Memory not in classpath, not attempting 44 | ``` 45 | 46 | ## Directory selection 47 | 48 | Next, you need to ensure the data directory is DAX enabled. Here are two possible approaches 49 | 50 | ### Option A: 51 | 52 | Symlink the directory to one that is on DAX. 53 | This is best where you've mounted `-o dax=always` and would like to leave the rest of the server data directory on a non-DAX fs. 54 | 55 | ```bash 56 | $ cd server/data/sifs 57 | $ ln -s /mnt/pmem/sifsdata data 58 | ``` 59 | 60 | ### Option B: 61 | 62 | Selectively enable DAX for the data directory. This is best where you've mounted `-o dax=inode` and default most files to non-DAX mode. 63 | 64 | ```bash 65 | $ cd server/data/sifs 66 | xfs_io -c 'lsattr -v' data 67 | [has-xattr] data 68 | $ xfs_io -c 'chattr +x' data 69 | $ xfs_io -c 'lsattr -v' data 70 | [dax, has-xattr] data 71 | ``` 72 | 73 | ## Validation 74 | 75 | You can check the directory is DAX enabled by having the pmem library try it out 76 | 77 | ```bash 78 | $ java -cp "lib/*" io.mashona.logwriting.PmemUtil server/data/sifs/data 79 | server/data/sifs/data: pmem is true 80 | ``` 81 | 82 | When the server starts, you can also look for the .pmem metadata file, which exists only when running in persistent memory mode 83 | 84 | ```bash 85 | $ ls server/data/sifs/data 86 | ispn12.0 ispn12.0.pmem 87 | ``` 88 | 89 | You can also use mashona's TRACE logging to check the library is installed and operating as expected. Edit `server/conf/log4j2.xml` 90 | 91 | ```xml 92 | 93 | 94 | ``` 95 | 96 | Then look in `server/log/server.log` 97 | 98 | ```log 99 | server/log/server.log.2021-04-14-2:2021-04-14 09:42:32,994 TRACE (blocking-thread--p3-t2) \ 100 | [io.mashona.logwriting.PmemUtil] pmemChannelFor:153 entry with file=/test/infinispan-server-12.1.0.Final/server/data/sifs/data/ispn12.0, length=16,777,216, create=false, readSharedMetadata=true 101 | server/log/server.log.2021-04-14-2:2021-04-14 09:42:33,000 TRACE (blocking-thread--p3-t2) \] 102 | [io.mashona.logwriting.PmemUtil] pmemChannelFor:194 exit returning io.mashona.logwriting.MappedFileChannel@4ca2f180 103 | ``` 104 | 105 | ## Cautionary Notes 106 | 107 | Always configure the system for Persistent Memory before populating the cache. 108 | Moving existing stores between persistent memory and regular storage in either direction is not supported. 109 | 110 | When backing up the store from persistent memory, ensure you also copy the .pmem metadata files. 111 | -------------------------------------------------------------------------------- /docs/_posts/2021-04-16-pmem-in-containers.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Using Persistent Memory in Containers" 4 | date: 2021-04-16 5 | --- 6 | 7 | With [linux containers](https://opencontainers.org/) a widespread tool for both development use and runtime deployments, 8 | it's interesting to look at how persistent memory can be accessed in container environments. 9 | 10 | Let's try building mashona inside a container. The unit tests executed by the build rely on having access to a persistent memory (dax-fs) filesystem, 11 | so having them pass is a good way to validate that the pmem can be used inside the container. 12 | 13 | ## Bootstrap 14 | 15 | First we're going to need a base image. Here we use the Red Hat RHEL8 [UBI](https://developers.redhat.com/blog/2020/03/24/red-hat-universal-base-images-for-docker-users/), 16 | but any similar linux base will do. 17 | 18 | The container initialization must expose some of the host's persistent memory filesystem to the container. 19 | Here the host has a pmem DAX enabled filesystem mounted at `/mnt/pmem`, 20 | and we're going to map a `forcontainer` directory on that to the guest as `/hostpmem` 21 | 22 | ```bash 23 | [host]$ mkdir /mnt/pmem/forcontainer 24 | [host]$ docker run -it --name pmemdemo --mount src=/mnt/pmem/forcontainer,target=/hostpmem,type=bind registry.access.redhat.com/ubi8/ubi:8.1 bash 25 | ``` 26 | ## Tools 27 | 28 | Next we need to install the build environment in the container. 29 | 30 | The RHEL base has OpenJDK packages only for the LTS Java releases. 31 | Persistent Memory won't be in one of those until Java 17, so for now we need a tarball release instead. 32 | 33 | We're also going to need the maven build tool. Although that is available as an .rpm, the package depends on the older java-11-openjdk, 34 | so you'll wind up with two versions of Java taking up space in the container if you go down that route. 35 | 36 | You can find current versions on the download pages for [Java](https://jdk.java.net/) and [Apache-Maven](http://maven.apache.org/download.cgi) 37 | 38 | ```bash 39 | [rhel8-ubi]$ mkdir pmemdemo; cd pmemdemo; 40 | [rhel8-ubi]$ yum install wget git 41 | [rhel8-ubi]$ wget https://download.java.net/java/GA/jdk16/7863447f0ab643c585b9bdebf67c69db/36/GPL/openjdk-16_linux-x64_bin.tar.gz 42 | [rhel8-ubi]$ tar -xf openjdk-16_linux-x64_bin.tar.gz 43 | [rhel8-ubi]$ wget https://downloads.apache.org/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz 44 | [rhel8-ubi]$ tar -xf apache-maven-3.8.1-bin.tar.gz 45 | [rhel8-ubi]$ export PATH=$PATH:/pmemdemo/jdk-16/bin:/pmemdemo/apache-maven-3.8.1/bin 46 | ``` 47 | 48 | Alternatively, with base images such as `fedora:33` it's possible to install the jdk with the package manager instead - `yum install java-latest-openjdk-devel` will currently get you jdk-16. 49 | 50 | ## Build 51 | 52 | Now that we have a build environment, we need something to build. 53 | Clone the mashona repository from github 54 | 55 | ```bash 56 | [rhel8-ubi]$ git clone https://github.com/jhalliday/mashona.git 57 | [rhel8-ubi]$ cd mashona/logwriting 58 | ``` 59 | 60 | Finally we're ready to run the build and tests 61 | 62 | ```bash 63 | [rhel8-ubi]$ mvn clean install -Djdk.dir=/pmemdemo/jdk-16 -Dpmem.test.dir=/hostpmem/testdir 64 | ``` 65 | 66 | ## Bonus Points: CPU pinning 67 | 68 | As persistent memory performance is sensitive to cross-socket access, for production workloads where latency is more critical than memory bandwidth, 69 | it can be desirable to place persistent memory filesystems on the memory attached to a specific socket. 70 | 71 | In such cases, apps running on the host OS can use `numactl` to pin the JVM process to the corresponding CPU cores. 72 | For containerised workloads, the container itself can be similarly pinned, 73 | using the [configuration](https://docs.docker.com/config/containers/resource_constraints/#cpu) flag `docker run --cpuset-cpus=[list|range]` 74 | -------------------------------------------------------------------------------- /docs/_posts/2021-06-15-the-lts-cometh.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "The LTS cometh" 4 | date: 2021-06-14 5 | --- 6 | 7 | The forthcoming [JDK 17](http://jdk.java.net/17/) release is an important event for support of persistent memory applications on the JVM. 8 | Although the [JEP 352: Non-Volatile Mapped Byte Buffers](https://openjdk.java.net/jeps/352) feature work was complete way back in JDK 14, 9 | the JDK 17 release will be the first LTS version to include it. 10 | 11 | Many developers benefit from the biannual release cadence and upgrade every six months to use the most feature-rich version of the platform, 12 | but there are still some environments that are more conservative and move only from one LTS release to the next. 13 | For such users upgrading from JDK 11, this will be the first opportunity to benefit from the new persistent memory support and other features in the platform. 14 | 15 | With the JDK release cycle now entering the rampdown phase, 16 | it's time to start testing on JDK 17 early-access builds. 17 | Pre-release testing allows us to identify potential issues early and provide feedback to the JDK developers whilst there is still time for changes to be made. 18 | 19 | The testing is not a large task. 20 | The Java platform API remains very stable over time and recompilation is straightforward. 21 | 22 | Until the test suite breaks... 23 | 24 | There is one change in the Java library internals between Java 16 and 17 that affects the mashona library. 25 | It's actually a feature enhancement, not a regression. 26 | Which is some consolation when staring at a nasty red test failure. 27 | 28 | In older Java releases, the MappedByteBuffer API has a potentially problematic quirk - 29 | duplicates and slices from a direct buffer are not themselves direct. 30 | What this mean in practice is that until now calling force() on the original object would do the right thing, 31 | ensuring that changes were flushed to persistent disk or, in the case of mashona, to persistent memory. 32 | But... calling force() on a duplicate or slice would silently fail to flush anything. 33 | Adding to the headaches, it's not easy to determine if you have the original object or a copy, so you can't always spot the problem. 34 | 35 | In part to get around this, mashona is designed to flush only through a PersistenceHandle, which wraps the original buffer. 36 | The library uses duplicates and slices freely for data copying, as it makes managing the arithmetic for offsets much easier and more readable, but it never calls flush on any of them. 37 | 38 | The library also provides a safety net in the form of the setParanoid() configuration flag, 39 | an option which causes the mashona to use reflection to peer inside the user-provided buffer and determine that it actually is an original and not an inferior copy. 40 | Of course this feature has a test, and it's this that breaks when running on JDK 17. 41 | 42 | The new JDK finally has a fix [ [1](https://bugs.openjdk.java.net/browse/JDK-4833719), [2](https://github.com/openjdk/jdk/pull/2902) ] for the longstanding quirk in the standard library, 43 | causing duplicated and sliced buffers to carry over the original file handle and thus flush correctly. 44 | So naturally our test complains it no longer gets an error when doing something that used to be expected to fail, but now doesn't. 45 | 46 | I guess we'll call that a Good Thing, though it does require some code changes to the library. 47 | 48 | Starting with 1.0.0.Beta2, official releases will be compiled on (and for) JDK 17 or later. 49 | The setParanoid option is gone, since it's no longer useful. 50 | However, the PeristenceHandle abstraction stays, as it's still a handy API design. 51 | 52 | In the unlikely event that you need the library to run on the obsolete JDK 14, 15 or 16 platforms after the release of 17, 53 | it's still possible to build the library from source. 54 | However, if you're building releases later than 1.0.0.Beta1 for those targets, 55 | you'll not have the setParanoid functionality available to you. 56 | Take particular care with the use of force() when developing your own persistent memory applications on JDK 17 but releasing them for use on older JDKs. 57 | 58 | +1 for unit testing and +100 for JDK enhancements. 59 | Remember kids, upgrade early, upgrade often, and always run your tests. 60 | -------------------------------------------------------------------------------- /docs/_posts/2021-08-03-beta2.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Mashona 1.0 Beta2" 4 | date: 2021-08-03 5 | --- 6 | 7 | As the [JDK 17](http://jdk.java.net/17/) release cycle hits release candidate stage ahead of the expected GA next month, so too is mashona taking the final steps towards its 1.0 release. 8 | 9 | The 1.0 Beta2 of the [logwriting](https://github.com/jhalliday/mashona/tree/main/logwriting) module out today 10 | brings code enhancements and changes to the [debug/trace logging](https://mashona.io/blog/2021/04/12/debug-logging). 11 | Feature work for 1.0 is now complete and the 1.0.GA release should follow shortly after the JDK 17 GA in September. 12 | 13 | The next few weeks are a good time for both testing the 1.0 version in your apps and to submit feature requests for the 1.1 development cycle. 14 | -------------------------------------------------------------------------------- /docs/_posts/2021-09-15-ga-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Mashona 1.0 Released" 4 | date: 2021-09-15 5 | --- 6 | 7 | Mashona 1.0 GA is now available, providing fast logging to persistent memory for Java applications. 8 | 9 | Using Java 17's [MappedByteBuffer](https://mashona.io/blog/2020/03/16/jep-352) 10 | support for DAX operations, mashona needs no native library component and avoids the overhead of JNI. 11 | 12 | Providing a variety of log types, it's flexible enough for a many use cases, 13 | integrating with e.g. the [Narayana](https://mashona.io/blog/2020/10/29/narayana) transaction manager 14 | and the [Infinispan](https://mashona.io/blog/2020/11/05/infinispan) data grid. 15 | 16 | -------------------------------------------------------------------------------- /docs/blog/atom.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: none 3 | --- 4 | 5 | 6 | 7 | Mashona Project Blog 8 | 9 | 10 | {{ site.time | date_to_xmlschema }} 11 | https://jhalliday.github.io/mashona/blog 12 | 13 | Jonathan Halliday 14 | jonathan.halliday.remove.me@redhat.com 15 | 16 | 17 | {% for post in site.posts %} 18 | 19 | {{ post.title }} 20 | 21 | {{ post.date | date_to_xmlschema }} 22 | https://jhalliday.github.io/mashona{{ post.id }} 23 | {{ post.content | xml_escape }} 24 | 25 | {% endfor %} 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/blog/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Mashona Project Blog 4 | --- 5 |

{{ page.title }}

6 | 12 | -------------------------------------------------------------------------------- /docs/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 60px auto; 3 | width: 70%; 4 | } 5 | nav ul, footer ul { 6 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 7 | padding: 0px; 8 | list-style: none; 9 | font-weight: bold; 10 | } 11 | nav ul li, footer ul li { 12 | display: inline; 13 | margin-right: 20px; 14 | } 15 | a { 16 | text-decoration: none; 17 | color: #999; 18 | } 19 | a:hover { 20 | text-decoration: underline; 21 | } 22 | h1 { 23 | font-size: 3em; 24 | font-family:'Helvetica', 'Arial', 'Sans-Serif'; 25 | } 26 | p { 27 | font-size: 1.5em; 28 | line-height: 1.4em; 29 | color: #333; 30 | } 31 | footer { 32 | border-top: 1px solid #d5d5d5; 33 | font-size: .8em; 34 | } 35 | 36 | ul.posts { 37 | margin: 20px auto 40px; 38 | font-size: 1.5em; 39 | } 40 | 41 | ul.posts li { 42 | list-style: none; 43 | } 44 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Mashona, Persistent Memory library for pure Java 4 | --- 5 |
6 |

We've added native support for Persistent Memory to the JDK, then built this library on top of it.

7 |

Docs and the Blog.

8 |
9 | -------------------------------------------------------------------------------- /logwriting-benchmark/src/main/java/io/mashona/logwriting/perftest/AppendOnlyLogBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting.perftest; 14 | 15 | import io.mashona.logwriting.AppendOnlyLog; 16 | import io.mashona.logwriting.AppendOnlyLogImpl; 17 | import jdk.nio.mapmode.ExtendedMapMode; 18 | import org.openjdk.jmh.annotations.*; 19 | import org.openjdk.jmh.runner.Runner; 20 | import org.openjdk.jmh.runner.options.Options; 21 | import org.openjdk.jmh.runner.options.OptionsBuilder; 22 | import sun.misc.Unsafe; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.lang.reflect.Field; 27 | import java.nio.MappedByteBuffer; 28 | import java.nio.channels.FileChannel; 29 | import java.nio.file.Files; 30 | import java.nio.file.StandardOpenOption; 31 | import java.util.Arrays; 32 | import java.util.EnumSet; 33 | 34 | /** 35 | * JMH benchmarking code for writing to an AppendOnlyLog. 36 | * 37 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 38 | * @since 2020-09 39 | */ 40 | @State(Scope.Benchmark) 41 | public class AppendOnlyLogBenchmark { 42 | 43 | static Unsafe unsafe; 44 | 45 | static { 46 | try { 47 | Field f = Unsafe.class.getDeclaredField("theUnsafe"); 48 | f.setAccessible(true); 49 | unsafe = (Unsafe) f.get(null); 50 | } catch (Exception e) { 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | private static File file = new File(System.getenv("PMEM_TEST_DIR"), "AppendOnlyLogBenchmark"); 56 | 57 | private static final int length = 1024 * 1024 * 512; 58 | 59 | private FileChannel fileChannel; 60 | private MappedByteBuffer mappedByteBuffer; 61 | private AppendOnlyLog appendOnlyLog; 62 | 63 | @Param({"1801"}) 64 | public int dataSize; 65 | 66 | @Param({"false", "true"}) 67 | public boolean blockPadding; 68 | 69 | private byte[] data; 70 | 71 | @State(Scope.Thread) 72 | @AuxCounters(AuxCounters.Type.OPERATIONS) 73 | public static class OpCounters { 74 | public long write; 75 | public long reset; 76 | } 77 | 78 | private void deleteFile() { 79 | if (file.exists()) { 80 | file.delete(); 81 | } 82 | } 83 | 84 | @Setup(Level.Iteration) 85 | public void setUp() throws IOException { 86 | 87 | deleteFile(); 88 | 89 | data = new byte[dataSize]; 90 | 91 | fileChannel = (FileChannel) Files 92 | .newByteChannel(file.toPath(), EnumSet.of( 93 | StandardOpenOption.READ, 94 | StandardOpenOption.WRITE, 95 | StandardOpenOption.CREATE)); 96 | 97 | mappedByteBuffer = fileChannel.map(ExtendedMapMode.READ_WRITE_SYNC, 0, length); 98 | 99 | appendOnlyLog = new AppendOnlyLogImpl(mappedByteBuffer, 0, length, blockPadding, false); 100 | 101 | Arrays.fill(data, (byte)-1); 102 | } 103 | 104 | @TearDown(Level.Iteration) 105 | public void tearDown() throws Exception { 106 | 107 | // https://bugs.openjdk.java.net/browse/JDK-4724038 108 | unsafe.invokeCleaner(mappedByteBuffer); 109 | 110 | fileChannel.close(); 111 | 112 | deleteFile(); 113 | } 114 | 115 | @Benchmark() 116 | @BenchmarkMode(Mode.Throughput) 117 | public void writeLog(OpCounters counters) { 118 | 119 | if(appendOnlyLog.tryPut(data)) { 120 | counters.write++; 121 | } else { 122 | synchronized (this) { 123 | if(!appendOnlyLog.canAccept(data.length)) { 124 | appendOnlyLog.clear(); 125 | counters.reset++; 126 | } 127 | } 128 | } 129 | } 130 | 131 | public static void main(String[] args) throws Exception { 132 | Options opt = new OptionsBuilder() 133 | .include(AppendOnlyLogBenchmark.class.getSimpleName()) 134 | .forks(0) // use 0 for debugging in-process 135 | .build(); 136 | new Runner(opt).run(); 137 | } 138 | } -------------------------------------------------------------------------------- /logwriting-benchmark/src/main/java/io/mashona/logwriting/perftest/ArrayStoreBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting.perftest; 14 | 15 | import io.mashona.logwriting.ArrayStore; 16 | import io.mashona.logwriting.ArrayStoreImpl; 17 | import org.openjdk.jmh.annotations.*; 18 | import org.openjdk.jmh.runner.Runner; 19 | import org.openjdk.jmh.runner.options.Options; 20 | import org.openjdk.jmh.runner.options.OptionsBuilder; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.Arrays; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | 27 | /** 28 | * JMH benchmarking code for writing to a MappedFileChannel. 29 | * 30 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 31 | * @since 2020-09 32 | */ 33 | @State(Scope.Benchmark) 34 | public class ArrayStoreBenchmark { 35 | 36 | private static File file = new File(System.getenv("PMEM_TEST_DIR"), "ArrayStoreBenchmark"); 37 | 38 | private ArrayStore arrayStore; 39 | 40 | @Param({"1801"}) 41 | public int dataSize; 42 | 43 | private byte[] data; 44 | 45 | private void deleteFile() { 46 | if (file.exists()) { 47 | file.delete(); 48 | } 49 | } 50 | 51 | @State(Scope.Thread) 52 | public static class ThreadId { 53 | public static AtomicInteger idAllocator = new AtomicInteger(0); 54 | public final int slot = idAllocator.getAndAdd(1); 55 | } 56 | 57 | @Setup(Level.Iteration) 58 | public void setUp() throws IOException { 59 | 60 | data = new byte[dataSize]; 61 | 62 | deleteFile(); 63 | 64 | arrayStore = new ArrayStoreImpl(file, 100, data.length); 65 | 66 | Arrays.fill(data, (byte)-1); 67 | } 68 | 69 | @TearDown(Level.Iteration) 70 | public void tearDown() throws Exception { 71 | 72 | arrayStore.close(); 73 | 74 | deleteFile(); 75 | } 76 | 77 | @Benchmark() 78 | @BenchmarkMode(Mode.Throughput) 79 | public void writeLog(ThreadId threadId) throws IOException { 80 | 81 | int slotIndex = threadId.slot; 82 | arrayStore.write(slotIndex, data); 83 | arrayStore.clear(slotIndex, false); 84 | } 85 | 86 | public static void main(String[] args) throws Exception { 87 | Options opt = new OptionsBuilder() 88 | .include(ArrayStoreBenchmark.class.getSimpleName()) 89 | .forks(0) // use 0 for debugging in-process 90 | .build(); 91 | new Runner(opt).run(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /logwriting-benchmark/src/main/java/io/mashona/logwriting/perftest/MappedFileChannelBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting.perftest; 14 | 15 | import io.mashona.logwriting.MappedFileChannel; 16 | import org.openjdk.jmh.annotations.*; 17 | import org.openjdk.jmh.runner.Runner; 18 | import org.openjdk.jmh.runner.options.Options; 19 | import org.openjdk.jmh.runner.options.OptionsBuilder; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.nio.ByteBuffer; 24 | import java.util.Arrays; 25 | 26 | /** 27 | * JMH benchmarking code for writing to a MappedFileChannel. 28 | * 29 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 30 | * @since 2020-09 31 | */ 32 | @State(Scope.Benchmark) 33 | public class MappedFileChannelBenchmark { 34 | 35 | private static File file = new File(System.getenv("PMEM_TEST_DIR"), "MappedFileChannelBenchmark"); 36 | 37 | private static final int length = 1024 * 1024 * 512; 38 | 39 | @Param({"1801"}) 40 | public int dataSize; 41 | 42 | private byte[] data; 43 | 44 | private MappedFileChannel mappedFileChannel; 45 | 46 | @State(Scope.Thread) 47 | @AuxCounters(AuxCounters.Type.OPERATIONS) 48 | public static class OpCounters { 49 | public long write; 50 | public long reset; 51 | } 52 | 53 | private void deleteFile() throws IOException { 54 | if (file.exists()) { 55 | file.delete(); 56 | MappedFileChannel.getMetadataFile(file).delete(); 57 | } 58 | } 59 | 60 | @Setup(Level.Iteration) 61 | public void setUp() throws IOException { 62 | 63 | data = new byte[dataSize]; 64 | 65 | deleteFile(); 66 | 67 | mappedFileChannel = new MappedFileChannel(file, length); 68 | 69 | Arrays.fill(data, (byte)-1); 70 | } 71 | 72 | @TearDown(Level.Iteration) 73 | public void tearDown() throws Exception { 74 | 75 | mappedFileChannel.close(); 76 | mappedFileChannel.deleteMetadata(); 77 | 78 | deleteFile(); 79 | } 80 | 81 | @Benchmark() 82 | @BenchmarkMode(Mode.Throughput) 83 | public void writeLog(OpCounters counters) throws IOException { 84 | 85 | if(data.length == mappedFileChannel.write(ByteBuffer.wrap(data))) { 86 | counters.write++; 87 | } else { 88 | synchronized (this) { 89 | if(mappedFileChannel.position() != 0) { 90 | mappedFileChannel.clear(); 91 | counters.reset++; 92 | } 93 | } 94 | } 95 | } 96 | 97 | public static void main(String[] args) throws Exception { 98 | Options opt = new OptionsBuilder() 99 | .include(MappedFileChannelBenchmark.class.getSimpleName()) 100 | .forks(0) // use 0 for debugging in-process 101 | .build(); 102 | new Runner(opt).run(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /logwriting-benchmark/src/main/java/io/mashona/logwriting/perftest/SimpleHardwareBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting.perftest; 14 | 15 | import jdk.nio.mapmode.ExtendedMapMode; 16 | import org.openjdk.jmh.annotations.*; 17 | import org.openjdk.jmh.runner.Runner; 18 | import org.openjdk.jmh.runner.options.Options; 19 | import org.openjdk.jmh.runner.options.OptionsBuilder; 20 | import org.openjdk.jmh.runner.options.TimeValue; 21 | import sun.misc.Unsafe; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.lang.reflect.Field; 26 | import java.nio.MappedByteBuffer; 27 | import java.nio.channels.FileChannel; 28 | import java.nio.file.Files; 29 | import java.nio.file.StandardOpenOption; 30 | import java.util.Arrays; 31 | import java.util.EnumSet; 32 | import java.util.concurrent.atomic.AtomicInteger; 33 | 34 | /** 35 | * JMH benchmarking code for writing to a MappedByteBuffer. 36 | * 37 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 38 | * @since 2020-10 39 | */ 40 | @State(Scope.Benchmark) 41 | public class SimpleHardwareBenchmark { 42 | 43 | static Unsafe unsafe; 44 | 45 | static { 46 | try { 47 | Field f = Unsafe.class.getDeclaredField("theUnsafe"); 48 | f.setAccessible(true); 49 | unsafe = (Unsafe) f.get(null); 50 | } catch (Exception e) { 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | private static File file = new File(System.getenv("PMEM_TEST_DIR"), "SimpleHardwareBenchmark"); 56 | 57 | private static final int length = 1024 * 1024 * 100; 58 | 59 | private FileChannel fileChannel; 60 | private MappedByteBuffer mappedByteBuffer; 61 | 62 | private static final byte[] data = new byte[1024*1024*10]; 63 | 64 | private void deleteFile() { 65 | if (file.exists()) { 66 | file.delete(); 67 | } 68 | } 69 | 70 | @Setup(Level.Iteration) 71 | public void setUp() throws IOException { 72 | 73 | deleteFile(); 74 | 75 | fileChannel = (FileChannel) Files 76 | .newByteChannel(file.toPath(), EnumSet.of( 77 | StandardOpenOption.READ, 78 | StandardOpenOption.WRITE, 79 | StandardOpenOption.CREATE)); 80 | 81 | mappedByteBuffer = fileChannel.map(ExtendedMapMode.READ_WRITE_SYNC, 0, length); 82 | //mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, length); 83 | 84 | Arrays.fill(data, (byte)-1); 85 | } 86 | 87 | @TearDown(Level.Iteration) 88 | public void tearDown() throws Exception { 89 | 90 | // https://bugs.openjdk.java.net/browse/JDK-4724038 91 | unsafe.invokeCleaner(mappedByteBuffer); 92 | 93 | fileChannel.close(); 94 | 95 | deleteFile(); 96 | } 97 | 98 | @State(Scope.Thread) 99 | public static class ThreadId { 100 | public static AtomicInteger idAllocator = new AtomicInteger(0); 101 | public final int offset = idAllocator.getAndAdd(1) * data.length; 102 | } 103 | 104 | @Benchmark() 105 | @BenchmarkMode(Mode.Throughput) 106 | public void writeLog(ThreadId threadId) { 107 | mappedByteBuffer.put(threadId.offset, data, 0, data.length); 108 | mappedByteBuffer.force(threadId.offset, data.length); 109 | } 110 | 111 | public static void main(String[] args) throws Exception { 112 | Options opt = new OptionsBuilder() 113 | .include(SimpleHardwareBenchmark.class.getSimpleName()) 114 | .forks(0) // use 0 for debugging in-process 115 | .measurementTime(TimeValue.hours(1)) 116 | .threads(4) 117 | .build(); 118 | new Runner(opt).run(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /logwriting/src/main/java/io/mashona/logwriting/AppendOnlyLogImplConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | /** 16 | * Configuration settings for the AppendOnlyLogImpl. 17 | * 18 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 19 | * @since 2021-09 20 | */ 21 | public class AppendOnlyLogImplConfig { 22 | 23 | private final boolean blockPadding; 24 | private final boolean linearOrdering; 25 | private final boolean alwaysCheckpoint; 26 | private final boolean authoritativeCheckpointOnReads; 27 | 28 | /** 29 | * Creates a new configuration object for an AppendOnlyLog. 30 | * 31 | *

Note that setting alwaysCheckpoint=true requires also that linearOrdering=true.

32 | *

Do not set authoritativeCheckpointOnReads=true when opening an existing log file, unless it was written with alwaysCheckpoint=true.

33 | * 34 | * @param blockPadding true if extra space should be used to increase performance, or false for a slower but more compact record format. 35 | * @param linearOrdering true if strict serial ordering of writes is required, false for more relaxed ordering guarantees. 36 | * @param alwaysCheckpointWrites true if automatic checkpointing of writes is required, false otherwise. 37 | * @param authoritativeCheckpointOnReads true if the persistent checkpoint (limit) in the file should be used when reading back the log, 38 | * false if the entries should be walked instead. 39 | * 40 | * @throws IllegalArgumentException if an unsupported combination of settings is used. 41 | */ 42 | public AppendOnlyLogImplConfig(boolean blockPadding, boolean linearOrdering, 43 | boolean alwaysCheckpointWrites, boolean authoritativeCheckpointOnReads) { 44 | 45 | if(alwaysCheckpointWrites && !linearOrdering) { 46 | throw new IllegalArgumentException("linearOrdering must be true when alwaysCheckpoint is enabled"); 47 | } 48 | 49 | this.blockPadding = blockPadding; 50 | this.linearOrdering = linearOrdering; 51 | this.alwaysCheckpoint = alwaysCheckpointWrites; 52 | this.authoritativeCheckpointOnReads = authoritativeCheckpointOnReads; 53 | } 54 | 55 | /** 56 | * Reports the padding mode. 57 | * 58 | * @return true if extra space should be used to increase performance, or false for a slower but more compact record format. 59 | */ 60 | public boolean isBlockPadding() { 61 | return blockPadding; 62 | } 63 | 64 | /** 65 | * Reports the ordering mode. 66 | * 67 | * @return true if strict serial ordering of writes is required, false for more relaxed ordering guarantees. 68 | */ 69 | public boolean isLinearOrdering() { 70 | return linearOrdering; 71 | } 72 | 73 | /** 74 | * Reports the auto-checkpointing mode. 75 | * 76 | * @return true if writes should always include an implicit checkpoint update, false otherwise. 77 | */ 78 | public boolean isAlwaysCheckpoint() { 79 | return alwaysCheckpoint; 80 | } 81 | 82 | /** 83 | * Reports the read/recovery mode. 84 | * 85 | * @return true if the persistent checkpoint (limit) in the file should be used when reading back the log, 86 | * false if the entries should be walked instead. 87 | */ 88 | public boolean isAuthoritativeCheckpointOnReads() { 89 | return authoritativeCheckpointOnReads; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "AppendOnlyLogImplConfig{" + 95 | "blockPadding=" + blockPadding + 96 | ", linearOrdering=" + linearOrdering + 97 | ", alwaysCheckpoint=" + alwaysCheckpoint + 98 | ", authoritativeCheckpointOnReads=" + authoritativeCheckpointOnReads + 99 | '}'; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /logwriting/src/main/java/io/mashona/logwriting/ArrayStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import java.io.IOException; 16 | import java.nio.ByteBuffer; 17 | 18 | /** 19 | * An array-like persistent storage structure with a number of fixed size slots, accessible concurrently. 20 | * The structure is zero-indexed, as with Java arrays or lists. 21 | *

22 | * Implementations are expected to provide safe concurrent access to different slots, 23 | * but not required to provide guarantees regarding concurrent access to the same slot. 24 | * 25 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 26 | * @since 2020-04 27 | */ 28 | public interface ArrayStore { 29 | 30 | /** 31 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 32 | * After this method returns successfully, the data is guaranteed persisted (i.e. flushed). 33 | * 34 | * @param slotIndex the location. 35 | * @param data the content. zero length content is equivalent to clearing the slot. 36 | */ 37 | default void write(int slotIndex, ByteBuffer data) throws IOException { 38 | write(slotIndex, data, true); 39 | } 40 | 41 | /** 42 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 43 | * 44 | * @param slotIndex the location. 45 | * @param data the content. zero length content is equivalent to clearing the slot. 46 | * @param force if the change should be immediately persistent or not. 47 | */ 48 | void write(int slotIndex, ByteBuffer data, boolean force) throws IOException; 49 | 50 | /** 51 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 52 | * After this method returns successfully, the data is guaranteed persisted (i.e. flushed). 53 | * 54 | * @param slotIndex the location. 55 | * @param data the content. zero length content is equivalent to clearing the slot. 56 | */ 57 | default void write(int slotIndex, byte[] data) throws IOException { 58 | write(slotIndex, data, true); 59 | } 60 | 61 | /** 62 | * Update the given slot with the provided data, overwriting (non-atomically) any existing data. 63 | * 64 | * @param slotIndex the location. 65 | * @param data the content. zero length content is equivalent to clearing the slot. 66 | * @param force if the change should be immediately persistent or not. 67 | */ 68 | void write(int slotIndex, byte[] data, boolean force) throws IOException; 69 | 70 | /** 71 | * Read the given slot, returning a copy of its contents. 72 | * 73 | * @param slotIndex the location. 74 | * @return a copy of the content, or null if the slot has not been written or has been cleared. 75 | */ 76 | ByteBuffer readAsByteBuffer(int slotIndex) throws IOException; 77 | 78 | /** 79 | * Read the given slot, returning a copy of its contents. 80 | * 81 | * @param slotIndex the location. 82 | * @return a copy of the content, or null if the slot has not been written or has been cleared. 83 | */ 84 | byte[] readAsByteArray(int slotIndex) throws IOException; 85 | 86 | /** 87 | * Update the given slot, discarding the contents. 88 | * After this method returns successfully, the data is guaranteed persisted (i.e. flushed). 89 | * 90 | * @param slotIndex location. 91 | * @param scrub overwrite the entire slot with zero data, which is slower than simply marking it invalid. 92 | */ 93 | default void clear(int slotIndex, boolean scrub) throws IOException { 94 | clear(slotIndex, scrub, true); 95 | } 96 | 97 | /** 98 | * Update the given slot, discarding the contents. 99 | * 100 | * @param slotIndex location. 101 | * @param scrub overwrite the entire slot with zero data, which is slower than simply marking it invalid. 102 | * @param force if the change should be immediately persistent or not. 103 | */ 104 | void clear(int slotIndex, boolean scrub, boolean force) throws IOException; 105 | 106 | /** 107 | * Close the store, preventing subsequent reads and writes. 108 | */ 109 | void close() throws IOException; 110 | } 111 | -------------------------------------------------------------------------------- /logwriting/src/main/java/io/mashona/logwriting/PersistenceHandle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.jboss.logging.Logger; 16 | 17 | import java.lang.reflect.Field; 18 | import java.nio.MappedByteBuffer; 19 | 20 | /** 21 | * A holder for a reference to a MappedByteBuffer backed by persistent memory, 22 | * via which data ranges may be flushed from cache to the persistence domain. 23 | * 24 | * Users of higher level abstractions should not normally need to use this class directly. 25 | * 26 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 27 | * @since 2019-04 28 | */ 29 | public class PersistenceHandle { 30 | 31 | private static final Logger logger = Logger.getLogger(PersistenceHandle.class); 32 | 33 | private final MappedByteBuffer buffer; 34 | private final int offset; 35 | private final int length; 36 | 37 | /** 38 | * Initializes a new PersistenceHandle for the specified region of the provided buffer. 39 | * 40 | *

Note that the MappedByteBuffer MUST be the original instance obtained 41 | * from fileChannel.map and NOT a duplicate or slice thereof.

42 | * 43 | * @param buffer The MappedByteBuffer over which to operate. 44 | * @param offset the offset in the buffer at which to base our operational area. 45 | * @param length the number of bytes in the operational area. 46 | */ 47 | public PersistenceHandle(MappedByteBuffer buffer, int offset, int length) { 48 | if(logger.isTraceEnabled()) { 49 | logger.tracev("entry with buffer={0}, offset={1}, length={2}", buffer, offset, length); 50 | } 51 | 52 | this.buffer = buffer; 53 | this.offset = offset; 54 | this.length = length; 55 | 56 | if(logger.isTraceEnabled()) { 57 | logger.tracev("exit {0}", this); 58 | } 59 | } 60 | 61 | // mostly for testing. 62 | PersistenceHandle duplicate(int offset, int length) { 63 | if(logger.isTraceEnabled()) { 64 | logger.tracev("entry for {0} with offset={1}, length={2}", this, offset, length); 65 | } 66 | 67 | if (length > this.length) { 68 | IllegalArgumentException illegalArgumentException = 69 | new IllegalArgumentException("Given length of " + length + " exceeds max of " + this.length); 70 | if(logger.isTraceEnabled()) { 71 | logger.tracev(illegalArgumentException, "throwing {0}", illegalArgumentException.toString()); 72 | } 73 | throw illegalArgumentException; 74 | } 75 | 76 | PersistenceHandle persistenceHandle = new PersistenceHandle(buffer, this.offset + offset, length); 77 | 78 | if(logger.isTraceEnabled()) { 79 | logger.tracev("exit returning {0}", persistenceHandle); 80 | } 81 | return persistenceHandle; 82 | } 83 | 84 | /** 85 | * Forces any changes made within the specified area to be written to the persistence domain. 86 | * 87 | * @param from the base offset. 88 | * @param length the number of bytes. 89 | */ 90 | public void persist(int from, int length) { 91 | if(logger.isTraceEnabled()) { 92 | logger.tracev("entry for {0} with from={1}, length={2}", this, from, length); 93 | } 94 | 95 | if (length > this.length) { 96 | IllegalArgumentException illegalArgumentException = 97 | new IllegalArgumentException("Given length of " + length + " exceeds max of " + this.length); 98 | if(logger.isTraceEnabled()) { 99 | logger.tracev(illegalArgumentException, "throwing {0}", illegalArgumentException); 100 | } 101 | throw illegalArgumentException; 102 | } 103 | 104 | buffer.force(from + offset, length); 105 | 106 | if(logger.isTraceEnabled()) { 107 | logger.tracev("exit"); 108 | } 109 | } 110 | 111 | /** 112 | * Forces any changes made to be written to the persistence domain. 113 | */ 114 | public void persist() { 115 | if(logger.isTraceEnabled()) { 116 | logger.tracev("entry for {0}", this); 117 | } 118 | 119 | persist(0, length); 120 | 121 | if(logger.isTraceEnabled()) { 122 | logger.tracev("exit"); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/ArrayStoreTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.params.ParameterizedTest; 19 | import org.junit.jupiter.params.provider.CsvSource; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.nio.ByteBuffer; 24 | import java.nio.channels.ClosedChannelException; 25 | import java.util.Arrays; 26 | 27 | import static org.junit.jupiter.api.Assertions.*; 28 | 29 | public class ArrayStoreTests { 30 | 31 | private static final File file = new File(System.getenv("PMEM_TEST_DIR"), "test"); 32 | private static final int NUMBER_OF_SLOTS = 100; 33 | private static final int SLOT_DATA_CAPACITY = 96; 34 | 35 | private ArrayStore arrayStore; 36 | 37 | @BeforeEach 38 | public void setUp() throws IOException { 39 | if (file.exists()) { 40 | file.delete(); 41 | } 42 | 43 | arrayStore = new ArrayStoreImpl(file, NUMBER_OF_SLOTS, SLOT_DATA_CAPACITY); 44 | } 45 | 46 | @AfterEach 47 | public void tearDown() { 48 | if (file.exists()) { 49 | file.delete(); 50 | } 51 | } 52 | 53 | @Test 54 | public void testConfigGetters() { 55 | assertEquals(NUMBER_OF_SLOTS, ((ArrayStoreImpl) arrayStore).getNumberOfSlots()); 56 | assertEquals(SLOT_DATA_CAPACITY, ((ArrayStoreImpl) arrayStore).getSlotDataCapacity()); 57 | } 58 | 59 | @ParameterizedTest 60 | @CsvSource({ 61 | "byte[], byte[]", 62 | "ByteBuffer, byte[]", 63 | "byte[], ByteBuffer", 64 | "ByteBuffer, ByteBuffer" 65 | }) 66 | public void testWriteAndReadBack(String readMethod, String writeMethod) throws IOException { 67 | 68 | for (int i = 0; i < NUMBER_OF_SLOTS; i++) { 69 | // write at least one byte and no more than slot size bytes, preferring to write exactly i bytes. 70 | int length = Math.max(1, Math.min(i, SLOT_DATA_CAPACITY)); 71 | byte[] data = new byte[length]; 72 | Arrays.fill(data, (byte) i); 73 | 74 | switch (writeMethod) { 75 | case "byte[]": 76 | arrayStore.write(i, data); 77 | case "ByteBuffer": 78 | arrayStore.write(i, ByteBuffer.wrap(data)); 79 | } 80 | } 81 | 82 | for (int i = 0; i < NUMBER_OF_SLOTS; i++) { 83 | 84 | byte[] data = null; 85 | switch (readMethod) { 86 | case "byte[]": 87 | data = arrayStore.readAsByteArray(i); 88 | case "ByteBuffer": 89 | ByteBuffer byteBuffer = arrayStore.readAsByteBuffer(i); 90 | data = byteBuffer.array(); // implementation specific shortcut. 91 | } 92 | 93 | int expectedLength = Math.max(1, Math.min(i, SLOT_DATA_CAPACITY)); 94 | assertEquals(expectedLength, data.length); 95 | for (int j = 0; j < expectedLength; j++) { 96 | assertEquals(i, data[j]); 97 | } 98 | } 99 | } 100 | 101 | @Test 102 | public void testClear() throws IOException { 103 | 104 | byte[] data = new byte[]{1}; 105 | arrayStore.write(0, data); 106 | byte[] read = arrayStore.readAsByteArray(0); 107 | assertArrayEquals(data, read); 108 | 109 | arrayStore.clear(0, false); 110 | assertNull(arrayStore.readAsByteArray(0)); 111 | assertNull(arrayStore.readAsByteBuffer(0)); 112 | 113 | arrayStore.write(0, data); 114 | read = arrayStore.readAsByteArray(0); 115 | assertArrayEquals(data, read); 116 | 117 | arrayStore.clear(0, true); 118 | assertNull(arrayStore.readAsByteArray(0)); 119 | assertNull(arrayStore.readAsByteBuffer(0)); 120 | } 121 | 122 | @Test 123 | public void testZeroLengthWrite() throws IOException { 124 | assertNull(arrayStore.readAsByteArray(0)); 125 | arrayStore.write(0, new byte[0]); 126 | assertNull(arrayStore.readAsByteArray(0)); 127 | } 128 | 129 | @Test 130 | public void testWriteTooLong() throws IOException { 131 | assertThrows(IOException.class, () -> arrayStore.write(0, new byte[SLOT_DATA_CAPACITY + 1])); 132 | } 133 | 134 | @Test 135 | public void testIndexOutOfBounds() throws IOException { 136 | assertThrows(ArrayIndexOutOfBoundsException.class, () -> arrayStore.readAsByteArray(-1)); 137 | assertThrows(ArrayIndexOutOfBoundsException.class, () -> arrayStore.readAsByteArray(NUMBER_OF_SLOTS)); 138 | } 139 | 140 | @Test 141 | public void testClosing() throws IOException { 142 | 143 | byte[] data = new byte[]{1}; 144 | arrayStore.write(0, data); 145 | 146 | arrayStore.close(); 147 | 148 | assertThrows(ClosedChannelException.class, () -> arrayStore.write(0, data)); 149 | assertThrows(ClosedChannelException.class, () -> arrayStore.readAsByteArray(0)); 150 | assertThrows(ClosedChannelException.class, () -> arrayStore.clear(0, false)); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/BMObjectAnnotationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.jboss.byteman.contrib.bmunit.*; 16 | import org.junit.jupiter.api.AfterEach; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.extension.*; 19 | import org.junit.platform.commons.support.HierarchyTraversalMode; 20 | 21 | import java.lang.annotation.Annotation; 22 | import java.lang.reflect.Method; 23 | import java.util.Optional; 24 | 25 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods; 26 | import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; 27 | 28 | public class BMObjectAnnotationHandler implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback { 29 | 30 | public static BMUnit5AbstractHandler[] HANDLERS = { 31 | new BMUnit5ConfigHandler(), 32 | new BMUnit5MultiScriptHandler(), 33 | new BMUnit5SingleScriptHandler(), 34 | new BMUnit5MultiRuleHandler(), 35 | new BMUnit5SingleRuleHandler() 36 | }; 37 | 38 | @Override 39 | public void afterEach(ExtensionContext context) throws Exception { 40 | invokeOnTarget(context, AfterEach.class); 41 | } 42 | 43 | @Override 44 | public void beforeEach(ExtensionContext context) throws Exception { 45 | invokeOnTarget(context, BeforeEach.class); 46 | } 47 | 48 | private void invokeOnTarget(ExtensionContext context, Class annotationType) throws Exception { 49 | 50 | final Class testClass = context.getRequiredTestClass(); 51 | final Optional optionalAnnotation = findAnnotation(testClass, WithBytemanFrom.class); 52 | if (optionalAnnotation.isEmpty()) { 53 | return; 54 | } 55 | 56 | final Class targetClass = optionalAnnotation.get().source(); 57 | 58 | for(Method method : findAnnotatedMethods(targetClass, annotationType, HierarchyTraversalMode.BOTTOM_UP)) { 59 | method.invoke(null); 60 | } 61 | } 62 | 63 | 64 | @Override 65 | public void beforeAll(ExtensionContext context) throws Exception { 66 | 67 | final Class testClass = context.getRequiredTestClass(); 68 | 69 | final Optional optionalAnnotation = findAnnotation(testClass, WithBytemanFrom.class); 70 | if (optionalAnnotation.isEmpty()) { 71 | return; 72 | } 73 | 74 | final Class targetClass = optionalAnnotation.get().source(); 75 | 76 | if (BMUnit.isBMUnitVerbose()) { 77 | System.out.println("installing " + targetClass.getCanonicalName()); 78 | } 79 | 80 | for (BMUnit5AbstractHandler handler : HANDLERS) { 81 | ExtensionContext dummyContext = new DummyExtensionContext(targetClass, null); 82 | handler.beforeAll(dummyContext); 83 | 84 | for (Object method : findAnnotatedMethods(targetClass, handler.getAnnotationClass(), HierarchyTraversalMode.BOTTOM_UP)) { 85 | ExtensionContext extensionContext = new DummyExtensionContext(testClass, (Method) method); 86 | handler.beforeEach(extensionContext); 87 | } 88 | } 89 | } 90 | 91 | @Override 92 | public void afterAll(ExtensionContext context) throws Exception { 93 | 94 | final Class testClass = context.getRequiredTestClass(); 95 | 96 | final Optional optionalAnnotation = findAnnotation(testClass, WithBytemanFrom.class); 97 | if (optionalAnnotation.isEmpty()) { 98 | return; 99 | } 100 | 101 | final Class targetClass = optionalAnnotation.get().source(); 102 | 103 | if (BMUnit.isBMUnitVerbose()) { 104 | System.out.println("uninstalling " + optionalAnnotation.get().source().getCanonicalName()); 105 | } 106 | 107 | ExtensionContext dummyContext = new DummyExtensionContext(targetClass, null); 108 | for (int i = HANDLERS.length; i > 0; i--) { 109 | HANDLERS[i - 1].afterAll(dummyContext); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/DummyExtensionContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.junit.jupiter.api.TestInstance; 16 | import org.junit.jupiter.api.extension.ExtensionContext; 17 | import org.junit.jupiter.api.extension.TestInstances; 18 | 19 | import java.lang.reflect.AnnotatedElement; 20 | import java.lang.reflect.Method; 21 | import java.util.Map; 22 | import java.util.Optional; 23 | import java.util.Set; 24 | 25 | public class DummyExtensionContext implements ExtensionContext { 26 | 27 | private final Class testClass; 28 | 29 | private final Method testMethod; 30 | 31 | public DummyExtensionContext(Class testClass, Method testMethod) { 32 | this.testClass = testClass; 33 | this.testMethod = testMethod; 34 | } 35 | 36 | 37 | @Override 38 | public Optional getParent() { 39 | return Optional.empty(); 40 | } 41 | 42 | @Override 43 | public ExtensionContext getRoot() { 44 | return null; 45 | } 46 | 47 | @Override 48 | public String getUniqueId() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public String getDisplayName() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public Set getTags() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public Optional getElement() { 64 | return Optional.empty(); 65 | } 66 | 67 | @Override 68 | public Optional> getTestClass() { 69 | return Optional.ofNullable(testClass); 70 | } 71 | 72 | @Override 73 | public Optional getTestInstanceLifecycle() { 74 | return Optional.empty(); 75 | } 76 | 77 | @Override 78 | public Optional getTestInstance() { 79 | return Optional.empty(); 80 | } 81 | 82 | @Override 83 | public Optional getTestInstances() { 84 | return Optional.empty(); 85 | } 86 | 87 | @Override 88 | public Optional getTestMethod() { 89 | return Optional.ofNullable(testMethod); 90 | } 91 | 92 | @Override 93 | public Optional getExecutionException() { 94 | return Optional.empty(); 95 | } 96 | 97 | @Override 98 | public Optional getConfigurationParameter(String key) { 99 | return Optional.empty(); 100 | } 101 | 102 | @Override 103 | public void publishReportEntry(Map map) { 104 | 105 | } 106 | 107 | @Override 108 | public Store getStore(Namespace namespace) { 109 | return null; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/MappedFileChannelMetadataTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.params.ParameterizedTest; 19 | import org.junit.jupiter.params.provider.CsvSource; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.nio.channels.ClosedChannelException; 24 | 25 | import static org.junit.jupiter.api.Assertions.*; 26 | import static org.junit.jupiter.api.Assertions.assertThrows; 27 | 28 | public class MappedFileChannelMetadataTests { 29 | 30 | private static File file = new File(System.getenv("PMEM_TEST_DIR"), "testmeta"); 31 | 32 | private MappedFileChannelMetadata mappedFileChannelMetadata; 33 | 34 | @BeforeEach 35 | public void setUp() throws IOException { 36 | 37 | if (file.exists()) { 38 | file.delete(); 39 | } 40 | if(!file.getParentFile().exists()) { 41 | file.getParentFile().mkdirs(); 42 | } 43 | } 44 | 45 | @AfterEach 46 | public void tearDown() throws IOException { 47 | if(mappedFileChannelMetadata != null) { 48 | mappedFileChannelMetadata.close(); 49 | } 50 | 51 | if (file.exists()) { 52 | file.delete(); 53 | } 54 | } 55 | 56 | @Test 57 | public void testInitializationFailure() throws IOException { 58 | 59 | IOException e = assertThrows(IOException.class, () -> new MappedFileChannelMetadata(new File("/tmp/bogus"))); 60 | assertEquals("Operation not supported", e.getMessage()); 61 | } 62 | 63 | @Test 64 | public void testInitialization() throws IOException { 65 | 66 | mappedFileChannelMetadata = new MappedFileChannelMetadata(file); 67 | assertEquals(0, mappedFileChannelMetadata.getPersistenceIndex()); 68 | } 69 | 70 | @ParameterizedTest 71 | @CsvSource({"false", "true"}) 72 | public void testWriteAndReadBack(boolean recover) throws IOException { 73 | 74 | mappedFileChannelMetadata = new MappedFileChannelMetadata(file); 75 | mappedFileChannelMetadata.persist(0, Integer.MAX_VALUE); 76 | 77 | if(recover) { 78 | mappedFileChannelMetadata.close(); 79 | mappedFileChannelMetadata = new MappedFileChannelMetadata(file); 80 | } 81 | 82 | assertEquals(Integer.MAX_VALUE, mappedFileChannelMetadata.getPersistenceIndex()); 83 | } 84 | 85 | @Test 86 | public void testExceptionWhenClosed() throws IOException { 87 | 88 | mappedFileChannelMetadata = new MappedFileChannelMetadata(file); 89 | mappedFileChannelMetadata.close(); 90 | 91 | assertThrows(ClosedChannelException.class, () -> mappedFileChannelMetadata.persist(0, 1)); 92 | assertThrows(ClosedChannelException.class, () -> mappedFileChannelMetadata.clear()); 93 | assertThrows(ClosedChannelException.class, () -> mappedFileChannelMetadata.getPersistenceIndex()); 94 | } 95 | 96 | @Test 97 | public void testReadSharing() throws IOException { 98 | 99 | mappedFileChannelMetadata = new MappedFileChannelMetadata(file); 100 | assertEquals(0, mappedFileChannelMetadata.getPersistenceIndex()); 101 | MappedFileChannelMetadata readFollower = new MappedFileChannelMetadata(file, true); 102 | assertEquals(0, readFollower.getPersistenceIndex()); 103 | 104 | mappedFileChannelMetadata.persist(0, 10); 105 | assertEquals(10, mappedFileChannelMetadata.getPersistenceIndex()); 106 | assertEquals(10, readFollower.getPersistenceIndex()); 107 | 108 | readFollower.persist(10, 10); 109 | assertEquals(10, mappedFileChannelMetadata.getPersistenceIndex()); 110 | assertEquals(20, readFollower.getPersistenceIndex()); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/PersistenceHandleTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import jdk.nio.mapmode.ExtendedMapMode; 16 | import org.junit.jupiter.api.AfterEach; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | import sun.misc.Unsafe; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.lang.reflect.Field; 24 | import java.nio.ByteBuffer; 25 | import java.nio.MappedByteBuffer; 26 | import java.nio.channels.FileChannel; 27 | import java.nio.file.Files; 28 | import java.nio.file.StandardOpenOption; 29 | import java.util.EnumSet; 30 | 31 | import static org.junit.jupiter.api.Assertions.assertThrows; 32 | 33 | @WithBytemanFrom(source = ExecutionTracer.class) 34 | public class PersistenceHandleTests { 35 | 36 | static Unsafe unsafe; 37 | 38 | static { 39 | try { 40 | Field f = Unsafe.class.getDeclaredField("theUnsafe"); 41 | f.setAccessible(true); 42 | unsafe = (Unsafe) f.get(null); 43 | } catch (Exception e) { 44 | throw new RuntimeException(e); 45 | } 46 | } 47 | 48 | private static File file = new File(System.getenv("PMEM_TEST_DIR"), "test"); 49 | 50 | private FileChannel fileChannel; 51 | private MappedByteBuffer mappedByteBuffer; 52 | 53 | @BeforeEach 54 | public void setUp() throws IOException { 55 | 56 | if (file.exists()) { 57 | file.delete(); 58 | } 59 | 60 | fileChannel = (FileChannel) Files 61 | .newByteChannel(file.toPath(), EnumSet.of( 62 | StandardOpenOption.READ, 63 | StandardOpenOption.WRITE, 64 | StandardOpenOption.CREATE)); 65 | 66 | mappedByteBuffer = fileChannel.map(ExtendedMapMode.READ_WRITE_SYNC, 0, 1024); 67 | } 68 | 69 | @AfterEach 70 | public void tearDown() throws IOException { 71 | 72 | // https://bugs.openjdk.java.net/browse/JDK-4724038 73 | unsafe.invokeCleaner(mappedByteBuffer); 74 | 75 | fileChannel.close(); 76 | 77 | if (file.exists()) { 78 | file.delete(); 79 | } 80 | } 81 | 82 | @Test 83 | public void testWholeRangePersistence() { 84 | 85 | PersistenceHandle persistenceHandle = new PersistenceHandle(mappedByteBuffer, 128, 128); 86 | 87 | mappedByteBuffer.put(128, new byte[128]); 88 | persistenceHandle.persist(); 89 | 90 | mappedByteBuffer.put(256, new byte[128]); 91 | persistenceHandle.duplicate(128, 128).persist(); 92 | } 93 | 94 | @Test 95 | public void testSubRangePersistence() { 96 | 97 | PersistenceHandle persistenceHandle = new PersistenceHandle(mappedByteBuffer, 128, 512); 98 | 99 | mappedByteBuffer.put(128, new byte[256]); 100 | persistenceHandle.persist(0, 256); 101 | 102 | mappedByteBuffer.put(384, new byte[256]); 103 | persistenceHandle.duplicate(128, 384).persist(128, 256); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/PmemUtilTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.io.File; 18 | import java.io.FileNotFoundException; 19 | import java.nio.channels.FileChannel; 20 | 21 | import static org.junit.jupiter.api.Assertions.*; 22 | 23 | public class PmemUtilTests { 24 | 25 | @Test 26 | public void testExceptionForBadPaths() { 27 | 28 | File notDir = new File("/dev/null"); 29 | assertThrows(IllegalArgumentException.class, () -> PmemUtil.isPmemSupportedFor(notDir)); 30 | 31 | File notAThing = new File("/bogus"); 32 | assertThrows(IllegalArgumentException.class, () -> PmemUtil.isPmemSupportedFor(notAThing)); 33 | } 34 | 35 | @Test 36 | public void testFailOnNonPmem() throws Exception { 37 | 38 | File testDir = new File("/tmp/foo"); 39 | testDir.mkdir(); 40 | File testFile = new File(testDir, "test"); 41 | 42 | FileChannel fileChannel = PmemUtil.pmemChannelFor(testFile, 1024, true); 43 | assertNull(fileChannel); 44 | assertEquals(0, testDir.listFiles().length); 45 | 46 | testFile.delete(); 47 | } 48 | 49 | @Test 50 | public void testSucceedOnPmem() throws Exception { 51 | 52 | File testDir = new File(System.getenv("PMEM_TEST_DIR"), "tx"); 53 | testDir.mkdir(); 54 | File testFile = new File(testDir, "test"); 55 | 56 | FileChannel fileChannel = PmemUtil.pmemChannelFor(testFile, 1024, true); 57 | assertNotNull(fileChannel); 58 | fileChannel.close(); 59 | assertEquals(2, testDir.listFiles().length); // file+metadata 60 | testFile.delete(); 61 | ((MappedFileChannel) fileChannel).deleteMetadata(); 62 | } 63 | 64 | @Test 65 | public void testNoCreation() throws Exception { 66 | 67 | File testDir = new File(System.getenv("PMEM_TEST_DIR")); 68 | testDir.mkdir(); 69 | File testFile = new File(testDir, "test"); 70 | 71 | assertFalse(testFile.exists()); 72 | 73 | assertThrows(FileNotFoundException.class, () -> PmemUtil.pmemChannelFor(testFile, 1024, false)); 74 | 75 | testFile.delete(); 76 | } 77 | 78 | @Test 79 | public void testArrayStore() throws Exception { 80 | 81 | File testDir = new File(System.getenv("PMEM_TEST_DIR"), "tx"); 82 | testDir.mkdir(); 83 | File testFile = new File(testDir, "test"); 84 | 85 | ArrayStore arrayStore = PmemUtil.arrayStoreFor(testFile, 10, 10); 86 | 87 | assertNotNull(arrayStore); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /logwriting/src/test/java/io/mashona/logwriting/WithBytemanFrom.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Red Hat 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.logwriting; 14 | 15 | import org.junit.jupiter.api.extension.ExtendWith; 16 | 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | 22 | @Target(ElementType.TYPE) 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @ExtendWith(BMObjectAnnotationHandler.class) 25 | public @interface WithBytemanFrom { 26 | 27 | Class source(); 28 | } 29 | -------------------------------------------------------------------------------- /logwriting/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %M:%L - %msg%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | logs/mashona_%d{yyyy-MM-dd}.log.gz 20 | 21 | 22 | 23 | 24 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} %M:%L %msg%n 25 | 26 | false 27 | 28 | 29 | 30 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /pobj/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 17 | 18 | 4.0.0 19 | 20 | io.mashona 21 | pobj 22 | 0.1.0-SNAPSHOT 23 | 24 | Mashona Persistent Objects 25 | 26 | 27 | UTF-8 28 | 14 29 | 14 30 | 31 | 5.4.0 32 | 4.0.10 33 | 1.21 34 | 35 | 36 | /usr/local/jdk-15 37 | ${jdk.dir}/bin/javac 38 | 39 | 40 | /mnt/pmem/test 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-clean-plugin 50 | 3.1.0 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-resources-plugin 56 | 3.1.0 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-compiler-plugin 67 | 3.8.0 68 | 69 | 70 | --add-modules=jdk.incubator.foreign 71 | 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-surefire-plugin 78 | 2.22.2 79 | 80 | 81 | ${pmem.test.dir} 82 | 83 | --add-modules=jdk.incubator.foreign 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-failsafe-plugin 90 | 2.22.2 91 | 92 | 93 | 94 | integration-test 95 | verify 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-source-plugin 104 | 3.0.1 105 | 106 | 107 | attach-sources 108 | 109 | jar 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | io.mashona 122 | logwriting 123 | 0.1.0-SNAPSHOT 124 | 125 | 126 | 127 | 128 | org.slf4j 129 | slf4j-api 130 | 1.7.26 131 | 132 | 133 | 134 | org.slf4j 135 | slf4j-ext 136 | 1.7.26 137 | 138 | 139 | 140 | ch.qos.logback 141 | logback-classic 142 | 1.2.3 143 | true 144 | 145 | 146 | 147 | 148 | org.junit.jupiter 149 | junit-jupiter-api 150 | ${junit.version} 151 | test 152 | 153 | 154 | org.junit.jupiter 155 | junit-jupiter-params 156 | ${junit.version} 157 | test 158 | 159 | 160 | org.junit.jupiter 161 | junit-jupiter-engine 162 | ${junit.version} 163 | test 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/PersistenceProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj; 14 | 15 | import java.nio.ByteBuffer; 16 | 17 | /** 18 | * Interface for support classes that provide persistence operations for a given type. 19 | * 20 | * @param The Object type that the provider can persist. 21 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 22 | * @since 2020-06 23 | */ 24 | public interface PersistenceProvider { 25 | 26 | /** 27 | * Persist the given object instance into the provided buffer, 28 | * starting at its current position. 29 | * This will advance the buffer's position by the objects size. 30 | * 31 | * @param t The object to persist. 32 | * @param byteBuffer The buffer into which to write the object state. 33 | */ 34 | void writeInto(T t, ByteBuffer byteBuffer); 35 | 36 | /** 37 | * Recreate an object instance by reading its persisted state from the provided buffer, 38 | * stating at its current position. 39 | * This will advance the buffer's position by the objects size. 40 | * 41 | * @param byteBuffer The buffer from which to read the object state. 42 | * @return An object instance 43 | */ 44 | T readFrom(ByteBuffer byteBuffer); 45 | } 46 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/allocator/CompositeAllocatorPersistence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import io.mashona.pobj.PersistenceProvider; 16 | 17 | import org.slf4j.ext.XLogger; 18 | import org.slf4j.ext.XLoggerFactory; 19 | 20 | import java.nio.ByteBuffer; 21 | import java.util.List; 22 | 23 | /** 24 | * Persists a CompositeAllocator, such that its state can be read/written to ByteBuffer. 25 | *

26 | * Instances of this class are stateless, but persistence is NOT threadsafe: 27 | * Snapshotting (i.e. 'serializing') a CompositeAllocator whilst it is servicing memory requests will have undefined results. 28 | * 29 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 30 | * @since 2020-06 31 | */ 32 | public class CompositeAllocatorPersistence implements PersistenceProvider { 33 | 34 | private static final XLogger logger = XLoggerFactory.getXLogger(CompositeAllocatorPersistence.class); 35 | 36 | private static final RegionBitmapPersistence REGION_BITMAP_PERSISTENCE = new RegionBitmapPersistence(); 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | @Override 42 | public void writeInto(CompositeAllocator compositeAllocator, ByteBuffer byteBuffer) { 43 | logger.entry(byteBuffer); 44 | 45 | byteBuffer.putLong(compositeAllocator.baseAddress); 46 | byteBuffer.putLong(compositeAllocator.backingSize); 47 | for (List list : compositeAllocator.regionBitmaps) { 48 | byteBuffer.putInt(list.size()); 49 | for (RegionBitmap regionBitmap : list) { 50 | REGION_BITMAP_PERSISTENCE.writeInto(regionBitmap, byteBuffer); 51 | } 52 | } 53 | 54 | logger.exit(); 55 | } 56 | 57 | /** 58 | * {@inheritDoc} 59 | */ 60 | @Override 61 | public CompositeAllocator readFrom(ByteBuffer byteBuffer) { 62 | logger.entry(byteBuffer); 63 | 64 | long baseAddress = byteBuffer.getLong(); 65 | long backingSize = byteBuffer.getLong(); 66 | 67 | CompositeAllocator instance = new CompositeAllocator(baseAddress, backingSize); 68 | instance.regionBitmaps[instance.regionConfigList.size() - 1].clear(); 69 | 70 | for (int i = 0; i < instance.regionBitmaps.length; i++) { 71 | int length = byteBuffer.getInt(); 72 | for (int j = 0; j < length; j++) { 73 | RegionBitmap regionBitmap = REGION_BITMAP_PERSISTENCE.readFrom(byteBuffer); 74 | instance.regionBitmaps[i].add(regionBitmap); 75 | } 76 | } 77 | 78 | logger.exit(instance); 79 | return instance; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/allocator/RegionBitmapPersistence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import io.mashona.pobj.PersistenceProvider; 16 | import org.slf4j.ext.XLogger; 17 | import org.slf4j.ext.XLoggerFactory; 18 | 19 | import java.nio.ByteBuffer; 20 | 21 | /** 22 | * Persists a RegionBitmap, such that its state can be read/written to ByteBuffer. 23 | *

24 | * Instances of this class are stateless, but persistence is NOT threadsafe: 25 | * Snapshotting (i.e. 'serializing') a RegionBitmap whilst it is servicing memory requests will have undefined results. 26 | * 27 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 28 | * @since 2020-06 29 | */ 30 | public class RegionBitmapPersistence implements PersistenceProvider { 31 | 32 | private static final XLogger logger = XLoggerFactory.getXLogger(RegionBitmapPersistence.class); 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | @Override 38 | public void writeInto(RegionBitmap regionBitmap, ByteBuffer byteBuffer) { 39 | logger.entry(byteBuffer); 40 | 41 | byteBuffer.putLong(regionBitmap.baseAddress); 42 | byteBuffer.putLong(regionBitmap.backingSize); 43 | byteBuffer.putLong(regionBitmap.elementSize); 44 | byteBuffer.asLongBuffer().put(regionBitmap.bitmap); 45 | byteBuffer.position(byteBuffer.position() + (regionBitmap.bitmapLength * Long.BYTES)); 46 | byteBuffer.putInt(regionBitmap.numAvail); 47 | byteBuffer.putInt(regionBitmap.nextAvail); 48 | 49 | logger.exit(); 50 | } 51 | 52 | /** 53 | * {@inheritD 54 | */ 55 | @Override 56 | public RegionBitmap readFrom(ByteBuffer byteBuffer) { 57 | logger.entry(); 58 | 59 | long baseAddress = byteBuffer.getLong(); 60 | long backingSize = byteBuffer.getLong(); 61 | long elementSize = byteBuffer.getLong(); 62 | 63 | RegionBitmap instance = new RegionBitmap(baseAddress, new RegionConfig(elementSize, backingSize)); 64 | 65 | byteBuffer.asLongBuffer().get(instance.bitmap); 66 | byteBuffer.position(byteBuffer.position() + (instance.bitmapLength * Long.BYTES)); 67 | instance.numAvail = byteBuffer.getInt(); 68 | instance.nextAvail = byteBuffer.getInt(); 69 | 70 | logger.exit(instance); 71 | return instance; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/allocator/RegionConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | /** 16 | * Describes the configuration of a memory region in terms of its overall size and the size of the elements it contains. 17 | * 18 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 19 | * @since 2020-06 20 | */ 21 | public class RegionConfig implements Comparable { 22 | 23 | private final long elementSize; 24 | private final long backingSize; 25 | 26 | /** 27 | * Create a encapsulation of the configuration properties of a contiguous memory region, 28 | * comprising an overall size and the unit size of divisions within it. 29 | * 30 | * @param elementSize the allocation unit size, in bytes. 31 | * @param backingSize the overall region size, in bytes. 32 | */ 33 | public RegionConfig(long elementSize, long backingSize) { 34 | this.elementSize = elementSize; 35 | this.backingSize = backingSize; 36 | } 37 | 38 | /** 39 | * Returns the element size for the region. 40 | * 41 | * @return the allocation unit size, in bytes. 42 | */ 43 | public long getElementSize() { 44 | return elementSize; 45 | } 46 | 47 | /** 48 | * Returns the total size of the region. 49 | * 50 | * @return the region memory size, in bytes. 51 | */ 52 | public long getBackingSize() { 53 | return backingSize; 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | @Override 60 | public int compareTo(RegionConfig o) { 61 | int x = Long.compare(elementSize, o.elementSize); 62 | if (x == 0) { 63 | x = Long.compare(backingSize, o.backingSize); 64 | } 65 | return x; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/runtime/MemoryBackedObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.runtime; 14 | 15 | /** 16 | * Interface for Objects whose state is mostly held in external memory, not on the JVM heap. 17 | * The Java Object implementing this interface is an on-heap stub, connecting to an off-heap 18 | * area of memory, accessed via the MemoryOperations interface, in which it can store state. 19 | * Implementations of this interface are expected to be largely stateless, 20 | * except for the MemoryOperations reference. Think of them as over-engineered type-safe pointers... 21 | * 22 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 23 | * @since 2020-06 24 | */ 25 | public interface MemoryBackedObject { 26 | 27 | /** 28 | * Provides the size of the object's required backing memory. 29 | * 30 | * @return the size, in bytes, of the required state storage space. 31 | */ 32 | int size(); 33 | 34 | /** 35 | * Sets the backing memory, connecting the on-heap stub to the off-heap store. 36 | * 37 | * @param memory An object through which the backing memory area may be accessed. 38 | */ 39 | void setMemory(MemoryOperations memory); 40 | 41 | /** 42 | * Gets the backing memory for the object, which may contains its state representation. 43 | * 44 | * @return An object through which the backing memory area may be accessed. 45 | */ 46 | MemoryOperations getMemory(); 47 | } 48 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/PersistentTransaction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.transaction.events.*; 16 | 17 | import org.slf4j.ext.XLogger; 18 | import org.slf4j.ext.XLoggerFactory; 19 | 20 | /** 21 | * A logged (i.e. fault-tolerant) transaction. 22 | * 23 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 24 | * @since 2020-07 25 | */ 26 | public class PersistentTransaction extends VolatileTransaction { 27 | 28 | private static final XLogger logger = XLoggerFactory.getXLogger(PersistentTransaction.class); 29 | 30 | private final TransactionStore transactionStore; 31 | 32 | /** 33 | * Create a new transaction instance that will be logged to the provided store. 34 | * 35 | * @param transactionStore the backing storage used to persist the transaction state. 36 | */ 37 | public PersistentTransaction(TransactionStore transactionStore) { 38 | this.transactionStore = transactionStore; 39 | } 40 | 41 | @Override 42 | protected void recordBeforeWriteEvent(BeforeWriteEvent beforeWriteEvent) { 43 | logger.entry(beforeWriteEvent); 44 | 45 | super.recordBeforeWriteEvent(beforeWriteEvent); 46 | transactionStore.persistBeforeWrite(beforeWriteEvent); 47 | 48 | logger.exit(); 49 | } 50 | 51 | @Override 52 | protected void recordMallocEvent(MallocEvent mallocEvent) { 53 | logger.entry(mallocEvent); 54 | 55 | super.recordMallocEvent(mallocEvent); 56 | transactionStore.persistMemoryAllocation(mallocEvent); 57 | 58 | logger.exit(); 59 | } 60 | 61 | @Override 62 | protected void recordDeallocateEvent(DeallocateEvent deallocateEvent) { 63 | logger.entry(deallocateEvent); 64 | 65 | super.recordDeallocateEvent(deallocateEvent); 66 | transactionStore.persistMemoryDelete(deallocateEvent); 67 | 68 | logger.exit(); 69 | } 70 | 71 | @Override 72 | protected void recordOutcomeEvent(OutcomeEvent outcomeEvent) { 73 | logger.entry(outcomeEvent); 74 | 75 | super.recordOutcomeEvent(outcomeEvent); 76 | transactionStore.persistOutcomeEvent(outcomeEvent); 77 | 78 | logger.exit(); 79 | } 80 | 81 | /** 82 | * Recover the transaction, bringing the state of the heap back to a consistent point. 83 | * 84 | * @param transactionalMemoryHeap The heap against which to apply the transactional changes. 85 | */ 86 | public void recover(TransactionalMemoryHeap transactionalMemoryHeap) { 87 | logger.entry(transactionalMemoryHeap); 88 | 89 | TransactionEvent lastEntry = events.get(events.size() - 1); 90 | if (lastEntry instanceof OutcomeEvent && ((OutcomeEvent) lastEntry).isCommit()) { 91 | redo(transactionalMemoryHeap); 92 | } else { 93 | undo(transactionalMemoryHeap); 94 | } 95 | 96 | logger.exit(); 97 | } 98 | 99 | void redo(TransactionalMemoryHeap transactionalPmemHeap) { 100 | for (TransactionEvent transactionEvent : events) { 101 | if (transactionEvent instanceof MallocEvent) { 102 | MallocEvent mallocTxEntry = (MallocEvent) transactionEvent; 103 | transactionalPmemHeap.getTransactionalCompositeAllocator().redo(mallocTxEntry); 104 | } else if (transactionEvent instanceof DeallocateEvent) { 105 | DeallocateEvent deleteTxEntry = (DeallocateEvent) transactionEvent; 106 | transactionalPmemHeap.getTransactionalCompositeAllocator().redo(deleteTxEntry); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/PersistentTransactionManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import org.slf4j.ext.XLogger; 16 | import org.slf4j.ext.XLoggerFactory; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * A factory for logged (i.e. fault-tolerant) transactions. 22 | * 23 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 24 | * @since 2020-07 25 | */ 26 | public class PersistentTransactionManager extends TransactionManager { 27 | 28 | private static final XLogger logger = XLoggerFactory.getXLogger(PersistentTransactionManager.class); 29 | 30 | private final TransactionStore transactionStore; 31 | 32 | /** 33 | * Create a new factory instance that will subsequently create transaction 34 | * instances that use the provided store for persistence. 35 | * 36 | * @param transactionStore the backing storage used to persist the transaction state. 37 | */ 38 | public PersistentTransactionManager(TransactionStore transactionStore) { 39 | this.transactionStore = transactionStore; 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | public void begin() { 47 | logger.entry(); 48 | 49 | currentTransaction = new PersistentTransaction(transactionStore); 50 | 51 | logger.exit(); 52 | } 53 | 54 | /** 55 | * Recover the transactions from the store, bringing the state of the heap back to a consistent point. 56 | * 57 | * @param transactionalMemoryHeap The heap against which to apply the transactional changes. 58 | */ 59 | public void recover(TransactionalMemoryHeap transactionalMemoryHeap) { 60 | logger.entry(transactionalMemoryHeap); 61 | 62 | List persistentTransactionList = transactionStore.read(); 63 | 64 | for (PersistentTransaction persistentTransaction : persistentTransactionList) { 65 | currentTransaction = new VolatileTransaction(); 66 | persistentTransaction.recover(transactionalMemoryHeap); 67 | currentTransaction = null; 68 | } 69 | 70 | logger.exit(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/TransactionManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import org.slf4j.ext.XLogger; 16 | import org.slf4j.ext.XLoggerFactory; 17 | 18 | /** 19 | * Provides management of transaction boundaries. 20 | * 21 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 22 | * @since 2020-07 23 | */ 24 | public class TransactionManager { 25 | 26 | private static final XLogger logger = XLoggerFactory.getXLogger(TransactionManager.class); 27 | 28 | private TransactionalMemoryHeap transactionalMemoryHeap; 29 | protected VolatileTransaction currentTransaction; 30 | 31 | /** 32 | * Create a new transaction 33 | */ 34 | public void begin() { 35 | logger.entry(); 36 | 37 | currentTransaction = new VolatileTransaction(); 38 | 39 | logger.exit(); 40 | } 41 | 42 | /** 43 | * Complete the current transaction 44 | */ 45 | public void commit() { 46 | logger.entry(); 47 | 48 | currentTransaction.commit(); 49 | 50 | logger.exit(); 51 | } 52 | 53 | /** 54 | * Rolls back the current transaction 55 | */ 56 | public void rollback() { 57 | logger.entry(); 58 | 59 | currentTransaction.rollback(transactionalMemoryHeap); 60 | 61 | logger.exit(); 62 | } 63 | 64 | protected void setTransactionalMemoryHeap(TransactionalMemoryHeap transactionalMemoryHeap) { 65 | this.transactionalMemoryHeap = transactionalMemoryHeap; 66 | } 67 | 68 | protected VolatileTransaction getCurrent() { 69 | return currentTransaction; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/TransactionalCompositeAllocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.allocator.CompositeAllocator; 16 | import io.mashona.pobj.transaction.events.DeallocateEvent; 17 | import io.mashona.pobj.transaction.events.MallocEvent; 18 | 19 | import org.slf4j.ext.XLogger; 20 | import org.slf4j.ext.XLoggerFactory; 21 | 22 | /** 23 | * Provides transactional memory tracking for allocations of varied sizes within a contiguous range, 24 | * by treating the overall space as dynamically composed of regions dedicated for each allocation size. 25 | *

26 | * This class provides bookkeeping only. The actual memory space being managed is theoretical 27 | * to the allocator and must be provided elsewhere, as by e.g. TransactionalHeap. 28 | *

29 | * This class is NOT threadsafe. 30 | * 31 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 32 | * @see CompositeAllocator 33 | * @since 2020-07 34 | */ 35 | public class TransactionalCompositeAllocator extends CompositeAllocator { 36 | 37 | private static final XLogger logger = XLoggerFactory.getXLogger(TransactionalCompositeAllocator.class); 38 | 39 | private final TransactionManager transactionManager; 40 | 41 | /** 42 | * Creates a new allocator with default region configuration, the overall area starting 43 | * at baseAddress and extending for backingSize bytes. 44 | *

45 | * A minimum backing size of 4MB is required and an 8-byte aligned base address is recommended. 46 | * 47 | * @param baseAddress The starting point of the memory range. 48 | * @param backingSize The total length of the memory region. 49 | * @param transactionManager the transaction manager to which state change events should be recorded. 50 | */ 51 | public TransactionalCompositeAllocator(long baseAddress, long backingSize, TransactionManager transactionManager) { 52 | super(baseAddress, backingSize); 53 | logger.entry(baseAddress, backingSize, transactionManager); 54 | 55 | this.transactionManager = transactionManager; 56 | 57 | logger.exit(); 58 | } 59 | 60 | @Override 61 | protected long allocate(long size, boolean forInternalUse) { 62 | long offset = allocate0(size, forInternalUse); 63 | if (!forInternalUse && offset != -1) { 64 | transactionManager.getCurrent().recordMallocEvent(new MallocEvent(offset, size, forInternalUse)); 65 | } 66 | return offset; 67 | } 68 | 69 | protected long allocate0(long size, boolean forInternalUse) { 70 | long offset = super.allocate(size, forInternalUse); 71 | return offset; 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | public void free(long address, long size) { 79 | logger.entry(address, size); 80 | 81 | free0(address, size); 82 | transactionManager.getCurrent().recordDeallocateEvent(new DeallocateEvent(address, size)); 83 | 84 | logger.exit(); 85 | } 86 | 87 | protected void free0(long address, long size) { 88 | super.free(address, size); 89 | } 90 | 91 | protected void redo(MallocEvent mallocEvent) { 92 | allocate0(mallocEvent.getSize(), false); 93 | } 94 | 95 | protected void redo(DeallocateEvent deallocateEvent) { 96 | free0(deallocateEvent.getOffset(), deallocateEvent.getSize()); 97 | } 98 | 99 | protected void undo(MallocEvent mallocEvent) { 100 | free0(mallocEvent.getOffset(), mallocEvent.getSize()); 101 | } 102 | 103 | protected void undo(DeallocateEvent deallocateEvent) { 104 | allocate0(deallocateEvent.getSize(), false); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/TransactionalMemoryHeap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.allocator.MemoryHeap; 16 | import io.mashona.pobj.runtime.MemoryOperations; 17 | import io.mashona.pobj.transaction.events.BeforeWriteEvent; 18 | 19 | import jdk.incubator.foreign.MemorySegment; 20 | import org.slf4j.ext.XLogger; 21 | import org.slf4j.ext.XLoggerFactory; 22 | 23 | import java.io.File; 24 | import java.io.IOException; 25 | 26 | /** 27 | * Manages a contiguous region of memory, mapped from a file, 28 | * as a heap space within which objects of varying size may be dynamically and transactionally allocated. 29 | *

30 | * Note that the allocation tracking is not done within the file itself 31 | * and should be persisted independently if required. 32 | *

33 | * Instances of this class are threadsafe if provided exclusive access to the underlying file and allocator. 34 | * If other instances (or external processes) access the same structures, all bets are off. 35 | * 36 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 37 | * @see MemoryHeap 38 | * @since 2020-07 39 | */ 40 | public class TransactionalMemoryHeap extends MemoryHeap { 41 | 42 | private static final XLogger logger = XLoggerFactory.getXLogger(TransactionalMemoryHeap.class); 43 | 44 | private final TransactionManager transactionManager; 45 | 46 | /** 47 | * Create a new heap abstraction over a given file, with memory use tracking as provided by the allocator 48 | * and transaction tracking provided by the transaction manager. 49 | * 50 | * @param file The backing file for persistent storage. 51 | * @param length The size in bytes of the memory region. Care should be taken that this is at least as big 52 | * as that configured for the provide allocator. 53 | * @param compositeAllocator The memory use bookkeeping object. 54 | * @param transactionManager The transaction coordination service to use. 55 | * @throws IOException if memory mapping of the file fails. 56 | */ 57 | public TransactionalMemoryHeap(File file, long length, TransactionalCompositeAllocator compositeAllocator, 58 | TransactionManager transactionManager) throws IOException { 59 | super(file, length, compositeAllocator); 60 | logger.entry(file, length, compositeAllocator, transactionManager); 61 | 62 | this.transactionManager = transactionManager; 63 | transactionManager.setTransactionalMemoryHeap(this); 64 | 65 | logger.exit(); 66 | } 67 | 68 | @Override 69 | protected MemoryOperations wrapMemory(long addr, MemorySegment memorySegment) { 70 | return new TransactionalMemoryOperations(addr, memorySegment, transactionManager); 71 | } 72 | 73 | protected TransactionalCompositeAllocator getTransactionalCompositeAllocator() { 74 | return (TransactionalCompositeAllocator) compositeAllocator; 75 | } 76 | 77 | protected void undo(BeforeWriteEvent beforeWriteEvent) { 78 | MemorySegment segment = memorySegment.asSlice(beforeWriteEvent.getOffset(), beforeWriteEvent.getSize()); 79 | segment.asByteBuffer().put(beforeWriteEvent.getByteBuffer()); 80 | beforeWriteEvent.getByteBuffer().rewind(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/TransactionalMemoryOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.runtime.MemoryOperations; 16 | import io.mashona.pobj.transaction.events.BeforeWriteEvent; 17 | import io.mashona.pobj.transaction.events.CreateEvent; 18 | import io.mashona.pobj.transaction.events.DeleteEvent; 19 | 20 | import jdk.incubator.foreign.MemorySegment; 21 | 22 | /** 23 | * Provides methods to store Java primitive types in a region of memory, 24 | * such as to allow an object to 'serialize' state to an off-heap store 25 | * at field granularity and access them in-place individually. 26 | * 27 | * Event interceptors are used to provide atomic transaction support, but not locking. 28 | * 29 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 30 | * @since 2020-07 31 | */ 32 | public class TransactionalMemoryOperations extends MemoryOperations { 33 | 34 | private boolean isDeleted; 35 | private final TransactionManager transactionManager; 36 | 37 | /** 38 | * Creates a new instance by wrapping the provided segment. 39 | * 40 | * @param heapOffset the base address of the object, relative to the heap it is allocated from. 41 | * @param memorySegment the area of backing memory to use. 42 | * @param transactionManager The transaction coordination service to use. 43 | */ 44 | public TransactionalMemoryOperations(long heapOffset, MemorySegment memorySegment, TransactionManager transactionManager) { 45 | super(heapOffset, memorySegment); 46 | this.transactionManager = transactionManager; 47 | transactionManager.getCurrent().recordCreateEvent(new CreateEvent(this)); 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | @Override 54 | public void delete() { 55 | delete0(); 56 | transactionManager.getCurrent().recordDeleteEvent(new DeleteEvent(this)); 57 | } 58 | 59 | protected void complete() { 60 | super.delete(); 61 | } 62 | 63 | protected void delete0() { 64 | isDeleted = true; 65 | } 66 | 67 | protected void undelete() { 68 | isDeleted = false; 69 | } 70 | 71 | /** 72 | * Access interception hook, invoked by getters prior to read operations on a part of the memory. 73 | * 74 | * An attempt to read a deleted object will result in a IllegalStateException. 75 | * 76 | * @param offset the starting offset within the memory. 77 | * @param length the size of the area being accessed (usually the size of a primitive datatype). 78 | * @throws IllegalStateException if the object has been deleted from the backing memory. 79 | */ 80 | @Override 81 | public void beforeRead(int offset, int length) { 82 | if (isDeleted) { 83 | throw new IllegalStateException(); 84 | } 85 | } 86 | 87 | /** 88 | * Access interception hook, invoked by setters prior to write operations on a part of the memory. 89 | * 90 | * An attempt to write a deleted object will result in a IllegalStateException. 91 | * An attempt to write a deleted object outside a transaction will result in an IllegalStateException 92 | * 93 | * @param offset the starting offset within the memory. 94 | * @param length the size of the area being accessed (usually the size of a primitive datatype). 95 | * @throws IllegalStateException if the object has been deleted from the backing memory, 96 | * or there is no transaction in progress. 97 | */ 98 | @Override 99 | public void beforeWrite(int offset, int length) { 100 | VolatileTransaction transaction = transactionManager.getCurrent(); 101 | if (isDeleted || transaction == null) { 102 | throw new IllegalStateException(); 103 | } 104 | 105 | BeforeWriteEvent beforeWriteEvent = new BeforeWriteEvent(heapOffset + offset, length, memorySegment.asSlice(offset, length).asByteBuffer()); 106 | 107 | transaction.recordBeforeWriteEvent(beforeWriteEvent); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/VolatileTransaction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.transaction.events.*; 16 | import org.slf4j.ext.XLogger; 17 | import org.slf4j.ext.XLoggerFactory; 18 | 19 | import java.nio.ByteBuffer; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * An in-memory (i.e. non-persistent) transaction. 25 | * 26 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 27 | * @since 2020-07 28 | */ 29 | public class VolatileTransaction { 30 | 31 | private static final XLogger logger = XLoggerFactory.getXLogger(VolatileTransaction.class); 32 | 33 | protected final List events = new ArrayList<>(); 34 | 35 | protected VolatileTransaction() { 36 | } 37 | 38 | protected void recordBeforeWriteEvent(BeforeWriteEvent beforeWriteEvent) { 39 | logger.entry(beforeWriteEvent); 40 | 41 | ByteBuffer byteBuffer = ByteBuffer.allocate((int) beforeWriteEvent.getSize()); // TODO force int, or require MemorySegment.copy 42 | byteBuffer.put(beforeWriteEvent.getByteBuffer()); 43 | byteBuffer.rewind(); 44 | 45 | beforeWriteEvent = new BeforeWriteEvent(beforeWriteEvent.getOffset(), beforeWriteEvent.getSize(), byteBuffer); 46 | events.add(beforeWriteEvent); 47 | 48 | logger.exit(); 49 | } 50 | 51 | protected void recordMallocEvent(MallocEvent mallocEvent) { 52 | logger.entry(mallocEvent); 53 | 54 | events.add(mallocEvent); 55 | 56 | logger.exit(); 57 | } 58 | 59 | protected void recordDeallocateEvent(DeallocateEvent deallocateEvent) { 60 | logger.entry(deallocateEvent); 61 | 62 | events.add(deallocateEvent); 63 | 64 | logger.exit(); 65 | } 66 | 67 | protected void recordDeleteEvent(DeleteEvent deleteEvent) { 68 | logger.entry(deleteEvent); 69 | 70 | events.add(deleteEvent); 71 | 72 | logger.exit(); 73 | } 74 | 75 | protected void recordCreateEvent(CreateEvent createEvent) { 76 | logger.entry(createEvent); 77 | 78 | events.add(createEvent); 79 | 80 | logger.exit(); 81 | } 82 | 83 | protected void commit() { 84 | logger.entry(); 85 | 86 | OutcomeEvent outcomeEvent = new OutcomeEvent(true); 87 | recordOutcomeEvent(outcomeEvent); 88 | 89 | complete(); 90 | 91 | logger.exit(); 92 | } 93 | 94 | protected void rollback(TransactionalMemoryHeap transactionalPmemHeap) { 95 | logger.entry(transactionalPmemHeap); 96 | 97 | OutcomeEvent outcomeEvent = new OutcomeEvent(false); 98 | recordOutcomeEvent(outcomeEvent); 99 | 100 | undo(transactionalPmemHeap); 101 | 102 | logger.exit(); 103 | } 104 | 105 | protected void recordOutcomeEvent(OutcomeEvent outcomeEvent) { 106 | events.add(outcomeEvent); 107 | } 108 | 109 | protected void complete() { 110 | for (int i = 0; i < events.size(); i++) { 111 | TransactionEvent event = events.get(i); 112 | if (event instanceof DeleteEvent) { 113 | DeleteEvent deleteEvent = (DeleteEvent) event; 114 | deleteEvent.getMemory().complete(); 115 | } 116 | } 117 | } 118 | 119 | protected void undo(TransactionalMemoryHeap transactionalPmemHeap) { 120 | 121 | for (int i = 0; i < events.size(); i++) { 122 | TransactionEvent event = events.get(i); 123 | if (event instanceof BeforeWriteEvent) { 124 | BeforeWriteEvent beforeWriteEvent = (BeforeWriteEvent) event; 125 | transactionalPmemHeap.undo(beforeWriteEvent); 126 | } 127 | } 128 | 129 | for (int i = events.size() - 1; i >= 0; i--) { 130 | TransactionEvent event = events.get(i); 131 | 132 | if (event instanceof MallocEvent) { 133 | MallocEvent mallocEvent = (MallocEvent) event; 134 | transactionalPmemHeap.getTransactionalCompositeAllocator().undo(mallocEvent); 135 | } 136 | if (event instanceof CreateEvent) { 137 | CreateEvent createEvent = (CreateEvent) event; 138 | createEvent.getMemory().delete0(); 139 | } 140 | 141 | if (event instanceof DeallocateEvent) { 142 | DeallocateEvent deallocateEvent = (DeallocateEvent) event; 143 | transactionalPmemHeap.getTransactionalCompositeAllocator().undo(deallocateEvent); 144 | } 145 | if (event instanceof DeleteEvent) { 146 | DeleteEvent deleteEvent = (DeleteEvent) event; 147 | deleteEvent.getMemory().undelete(); 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/BeforeWriteEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import java.nio.ByteBuffer; 16 | import java.util.Objects; 17 | 18 | /** 19 | * Transaction log entry for recording pre-modification state of an area of memory. 20 | * 21 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 22 | * @since 2020-07 23 | */ 24 | public class BeforeWriteEvent implements TransactionEvent { 25 | 26 | private final long offset; 27 | private final long size; 28 | private final ByteBuffer byteBuffer; 29 | 30 | /** 31 | * Create a transaction log entry capturing the pre-modification value of a region of memory. 32 | * 33 | * @param offset the starting location of the memory, measured from the base of the heap. 34 | * @param size the length of the memory region. 35 | * @param byteBuffer A buffer holding the memory value. This buffer will NOT be copied, 36 | * so must already be backed by memory independent of that about to be modified. 37 | */ 38 | public BeforeWriteEvent(long offset, long size, ByteBuffer byteBuffer) { 39 | this.offset = offset; 40 | this.size = size; 41 | this.byteBuffer = byteBuffer; 42 | } 43 | 44 | public long getOffset() { 45 | return offset; 46 | } 47 | 48 | public long getSize() { 49 | return size; 50 | } 51 | 52 | public ByteBuffer getByteBuffer() { 53 | return byteBuffer; 54 | } 55 | 56 | @Override 57 | public boolean equals(Object o) { 58 | if (this == o) return true; 59 | if (o == null || getClass() != o.getClass()) return false; 60 | BeforeWriteEvent that = (BeforeWriteEvent) o; 61 | return offset == that.offset && 62 | size == that.size && 63 | byteBuffer.equals(that.byteBuffer); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Objects.hash(offset, size, byteBuffer); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/BeforeWriteEventPersistence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import org.slf4j.ext.XLogger; 16 | import org.slf4j.ext.XLoggerFactory; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | /** 21 | * Persistence helper for (de)serializing BeforeWriteEvent records to a ByteBuffer backed transaction log. 22 | * 23 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 24 | * @since 2020-07 25 | */ 26 | public class BeforeWriteEventPersistence implements TransactionEventPersistence { 27 | 28 | private static final XLogger logger = XLoggerFactory.getXLogger(BeforeWriteEventPersistence.class); 29 | 30 | public static final long pmemUID = 0xFEFE; 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public long getFormatId() { 37 | return pmemUID; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public BeforeWriteEvent readFrom(ByteBuffer byteBuffer) { 45 | logger.entry(byteBuffer); 46 | 47 | long offset = byteBuffer.getLong(); 48 | long size = byteBuffer.getLong(); 49 | ByteBuffer dataBuffer = ByteBuffer.allocate((int) size); 50 | dataBuffer.put(byteBuffer.duplicate().limit(byteBuffer.position() + (int) size)); 51 | byteBuffer.position(byteBuffer.position() + (int) size); 52 | BeforeWriteEvent beforeWriteEvent = new BeforeWriteEvent(offset, size, dataBuffer.rewind()); 53 | 54 | logger.exit(beforeWriteEvent); 55 | return beforeWriteEvent; 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | @Override 62 | public void writeInto(BeforeWriteEvent beforeWriteEvent, ByteBuffer byteBuffer) { 63 | logger.entry(beforeWriteEvent, byteBuffer); 64 | 65 | byteBuffer.putLong(pmemUID); 66 | byteBuffer.putLong(beforeWriteEvent.getOffset()); 67 | byteBuffer.putLong(beforeWriteEvent.getSize()); 68 | byteBuffer.put(beforeWriteEvent.getByteBuffer().duplicate().rewind()); 69 | 70 | logger.exit(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/CreateEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import io.mashona.pobj.transaction.TransactionalMemoryOperations; 16 | 17 | /** 18 | * Transaction log entry for recording object memory segment open events. 19 | * Note these are volatile and distinct from the persistent MallocEvent. 20 | * 21 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 22 | * @since 2020-07 23 | */ 24 | public class CreateEvent implements TransactionEvent { 25 | 26 | private final TransactionalMemoryOperations memory; 27 | 28 | /** 29 | * Creates a record of the creation of a memory-backed object. 30 | * 31 | * @param memory the wrapper of the backing memory for the object. 32 | */ 33 | public CreateEvent(TransactionalMemoryOperations memory) { 34 | this.memory = memory; 35 | } 36 | 37 | public TransactionalMemoryOperations getMemory() { 38 | return memory; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/DeallocateEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import java.util.Objects; 16 | 17 | /** 18 | * Transaction log entry for recording memory release (i.e. free) operations. 19 | * 20 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 21 | * @since 2020-07 22 | */ 23 | public class DeallocateEvent implements TransactionEvent { 24 | 25 | private final long offset; 26 | private final long size; 27 | 28 | /** 29 | * Creates a record of the deletion/freeing of a heap memory allocation. 30 | * 31 | * @param offset the starting location of the memory, measured from the base of the heap. 32 | * @param size the length of the memory region. 33 | */ 34 | public DeallocateEvent(long offset, long size) { 35 | this.offset = offset; 36 | this.size = size; 37 | } 38 | 39 | public long getOffset() { 40 | return offset; 41 | } 42 | 43 | public long getSize() { 44 | return size; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | if (this == o) return true; 50 | if (o == null || getClass() != o.getClass()) return false; 51 | DeallocateEvent that = (DeallocateEvent) o; 52 | return offset == that.offset && 53 | size == that.size; 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return Objects.hash(offset, size); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/DeallocateEventPersistence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import org.slf4j.ext.XLogger; 16 | import org.slf4j.ext.XLoggerFactory; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | /** 21 | * Persistence helper for (de)serializing memory release (free) DeleteEvent records in a ByteBuffer backed transaction log. 22 | * 23 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 24 | * @since 2020-07 25 | */ 26 | public class DeallocateEventPersistence implements TransactionEventPersistence { 27 | 28 | private static final XLogger logger = XLoggerFactory.getXLogger(DeallocateEventPersistence.class); 29 | 30 | public static final long pmemUID = 0xFCFC; 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public long getFormatId() { 37 | return pmemUID; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public DeallocateEvent readFrom(ByteBuffer byteBuffer) { 45 | logger.entry(byteBuffer); 46 | 47 | long offset = byteBuffer.getLong(); 48 | long size = byteBuffer.getLong(); 49 | DeallocateEvent deallocateEvent = new DeallocateEvent(offset, size); 50 | 51 | logger.exit(deallocateEvent); 52 | return deallocateEvent; 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | @Override 59 | public void writeInto(DeallocateEvent deallocateEvent, ByteBuffer byteBuffer) { 60 | logger.entry(deallocateEvent, byteBuffer); 61 | 62 | byteBuffer.putLong(pmemUID); 63 | byteBuffer.putLong(deallocateEvent.getOffset()); 64 | byteBuffer.putLong(deallocateEvent.getSize()); 65 | 66 | logger.exit(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/DeleteEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import io.mashona.pobj.transaction.TransactionalMemoryOperations; 16 | 17 | /** 18 | * Transaction log entry for recording object memory segment close events. 19 | * Note these are volatile and distinct from the persistent DeallocateEvent. 20 | * 21 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 22 | * @since 2020-07 23 | */ 24 | public class DeleteEvent implements TransactionEvent { 25 | 26 | private final TransactionalMemoryOperations memory; 27 | 28 | /** 29 | * Creates a record of the deletion of a memory-backed object. 30 | * 31 | * @param memory the wrapper of the backing memory for the object. 32 | */ 33 | public DeleteEvent(TransactionalMemoryOperations memory) { 34 | this.memory = memory; 35 | } 36 | 37 | public TransactionalMemoryOperations getMemory() { 38 | return memory; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/MallocEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import java.util.Objects; 16 | 17 | /** 18 | * Transaction log entry for recording memory allocation operations. 19 | * 20 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 21 | * @since 2020-07 22 | */ 23 | public class MallocEvent implements TransactionEvent { 24 | 25 | private final long offset; 26 | private final long size; 27 | private final boolean forInternalUse; 28 | 29 | /** 30 | * Creates a record of the allocation of a region of memory from a heap. 31 | * 32 | * @param offset the starting location of the memory, measured from the base of the heap. 33 | * @param size the length of the memory region. 34 | * @param forInternalUse true if the memory is for bookkeeping use by the allocator itself, false for user requests. 35 | */ 36 | public MallocEvent(long offset, long size, boolean forInternalUse) { 37 | this.offset = offset; 38 | this.size = size; 39 | this.forInternalUse = forInternalUse; 40 | } 41 | 42 | public long getOffset() { 43 | return offset; 44 | } 45 | 46 | public long getSize() { 47 | return size; 48 | } 49 | 50 | public boolean isForInternalUse() { 51 | return forInternalUse; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) return true; 57 | if (o == null || getClass() != o.getClass()) return false; 58 | MallocEvent that = (MallocEvent) o; 59 | return offset == that.offset && 60 | size == that.size && 61 | forInternalUse == that.forInternalUse; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return Objects.hash(offset, size, forInternalUse); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/MallocEventPersistence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import org.slf4j.ext.XLogger; 16 | import org.slf4j.ext.XLoggerFactory; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | /** 21 | * Persistence helper for (de)serializing memory allocation MallocEvent records in a ByteBuffer backed transaction log. 22 | * 23 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 24 | * @since 2020-07 25 | */ 26 | public class MallocEventPersistence implements TransactionEventPersistence { 27 | 28 | private static final XLogger logger = XLoggerFactory.getXLogger(MallocEventPersistence.class); 29 | 30 | public static final long pmemUID = 0xFFFF; 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public long getFormatId() { 37 | return pmemUID; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public MallocEvent readFrom(ByteBuffer byteBuffer) { 45 | logger.entry(byteBuffer); 46 | 47 | long offset = byteBuffer.getLong(); 48 | long size = byteBuffer.getLong(); 49 | int forInternalUse = byteBuffer.getInt(); 50 | MallocEvent mallocEvent = new MallocEvent(offset, size, forInternalUse == 1); 51 | 52 | logger.exit(mallocEvent); 53 | return mallocEvent; 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | @Override 60 | public void writeInto(MallocEvent mallocEvent, ByteBuffer byteBuffer) { 61 | logger.entry(mallocEvent, byteBuffer); 62 | 63 | byteBuffer.putLong(pmemUID); 64 | byteBuffer.putLong(mallocEvent.getOffset()); 65 | byteBuffer.putLong(mallocEvent.getSize()); 66 | byteBuffer.putInt(mallocEvent.isForInternalUse() ? 1 : 0); 67 | 68 | logger.exit(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/OutcomeEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import java.util.Objects; 16 | 17 | /** 18 | * Transaction log entry for recording terminal commit/rollback decision. 19 | * 20 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 21 | * @since 2020-07 22 | */ 23 | public class OutcomeEvent implements TransactionEvent { 24 | 25 | public final boolean commit; 26 | 27 | /** 28 | * Creates a record of the terminal state of a transaction. 29 | * 30 | * @param commit true for committed transactions, false for those rolled back. 31 | */ 32 | public OutcomeEvent(boolean commit) { 33 | this.commit = commit; 34 | } 35 | 36 | public boolean isCommit() { 37 | return commit; 38 | } 39 | 40 | @Override 41 | public boolean equals(Object o) { 42 | if (this == o) return true; 43 | if (o == null || getClass() != o.getClass()) return false; 44 | OutcomeEvent that = (OutcomeEvent) o; 45 | return commit == that.commit; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(commit); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/OutcomeEventPersistence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import org.slf4j.ext.XLogger; 16 | import org.slf4j.ext.XLoggerFactory; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | /** 21 | * Persistence helper for (de)serializing terminal commit/rollback decision OutcomeEvent records in a ByteBuffer backed transaction log. 22 | * 23 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 24 | * @since 2020-07 25 | */ 26 | public class OutcomeEventPersistence implements TransactionEventPersistence { 27 | 28 | private static final XLogger logger = XLoggerFactory.getXLogger(OutcomeEventPersistence.class); 29 | 30 | public static final long pmemUID = 0xFDFD; 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public long getFormatId() { 37 | return pmemUID; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public OutcomeEvent readFrom(ByteBuffer byteBuffer) { 45 | logger.entry(byteBuffer); 46 | 47 | int commit = byteBuffer.getInt(); 48 | OutcomeEvent outcomeEvent = new OutcomeEvent(commit == 1); 49 | 50 | logger.exit(outcomeEvent); 51 | return outcomeEvent; 52 | } 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | @Override 58 | public void writeInto(OutcomeEvent outcomeEvent, ByteBuffer byteBuffer) { 59 | logger.entry(outcomeEvent, byteBuffer); 60 | 61 | byteBuffer.putLong(pmemUID); 62 | byteBuffer.putInt(outcomeEvent.commit ? 1 : 0); 63 | 64 | logger.exit(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/TransactionEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | /** 16 | * Marker interface for transaction log entry types. 17 | * 18 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 19 | * @since 2020-07 20 | */ 21 | public interface TransactionEvent { 22 | } 23 | -------------------------------------------------------------------------------- /pobj/src/main/java/io/mashona/pobj/transaction/events/TransactionEventPersistence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import java.nio.ByteBuffer; 16 | 17 | /** 18 | * Interface for helper classes that provide I/O support for persisting transaction events. 19 | * 20 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 21 | * @since 2020-07 22 | */ 23 | public interface TransactionEventPersistence { 24 | 25 | /** 26 | * Provides a unique value to distinguish a type of persisted record from others. 27 | * Broadly equivalent in role to Java serialization's serialVersionUID. 28 | * 29 | * @return a record type discriminator value, unique in the scope of the log. 30 | */ 31 | long getFormatId(); 32 | 33 | /** 34 | * Instantiate an object instance from its persisted form. 35 | *

36 | * Note that this method is not entirely symmetric with writeInto - the formatId discriminator prefix is 37 | * expected to be read prior to invoking this method, as it is required for dispatch to the correct 38 | * persistence instance for the type. 39 | * 40 | * @param byteBuffer a buffer containing the persisted state, which will be read and advanced from its current position. 41 | * @return T a recreated instance of the appropriate type 42 | */ 43 | T readFrom(ByteBuffer byteBuffer); 44 | 45 | /** 46 | * Persist an object instance into the provided buffer. 47 | *

48 | * Note that this method is expected to prefix the written object state with the format id, which is asymmetric 49 | * to the behaviour of readFrom, which does not expect to read that prefix. 50 | * 51 | * @param t a LoggableTransactionEvent instance. 52 | * @param byteBuffer the store in which state will be written, starting and advancing from the current position. 53 | */ 54 | void writeInto(T t, ByteBuffer byteBuffer); 55 | } 56 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/allocator/BogusMemoryBackedObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import io.mashona.pobj.runtime.MemoryBackedObject; 16 | import io.mashona.pobj.runtime.MemoryOperations; 17 | 18 | /** 19 | * Test case helper that purposefully has a private constructor, which is Not Allowed. 20 | * 21 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 22 | * @since 2020-06 23 | */ 24 | public class BogusMemoryBackedObject implements MemoryBackedObject { 25 | 26 | private MemoryOperations memory; 27 | 28 | private BogusMemoryBackedObject() { 29 | } 30 | 31 | @Override 32 | public void setMemory(MemoryOperations memory) { 33 | this.memory = memory; 34 | } 35 | 36 | @Override 37 | public int size() { 38 | return 8; 39 | } 40 | 41 | @Override 42 | public MemoryOperations getMemory() { 43 | return memory; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/allocator/CompositeAllocatorPersistenceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.nio.ByteBuffer; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | 21 | /** 22 | * Unit tests for persistence of CompositeAllocators. 23 | * 24 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 25 | * @since 2020-06 26 | */ 27 | public class CompositeAllocatorPersistenceTests { 28 | 29 | @Test 30 | public void testPersistable() { 31 | 32 | CompositeAllocator compositeAllocator = new CompositeAllocator(0, 1024 * 1024 * 8); 33 | CompositeAllocatorPersistence compositeAllocatorPersistence = new CompositeAllocatorPersistence(); 34 | 35 | for (int i = 0; i < (1024 * 1024 * 4) / 8; i++) { 36 | assertNotEquals(-1, compositeAllocator.allocate(8)); 37 | } 38 | 39 | for (int i = 0; i < (1024 * 1024 * 4) / 32; i++) { 40 | assertNotEquals(-1, compositeAllocator.allocate(32)); 41 | } 42 | 43 | assertEquals(-1, compositeAllocator.allocate(8)); 44 | assertEquals(-1, compositeAllocator.allocate(32)); 45 | 46 | ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 128); 47 | compositeAllocatorPersistence.writeInto(compositeAllocator, byteBuffer); 48 | byteBuffer.flip(); 49 | 50 | CompositeAllocator recoveredCompositeAllocator = compositeAllocatorPersistence.readFrom(byteBuffer); 51 | 52 | assertEquals(1024 * 1024 * 8L, recoveredCompositeAllocator.getBackingSize()); 53 | 54 | assertEquals(-1, recoveredCompositeAllocator.allocate(8)); 55 | assertEquals(-1, recoveredCompositeAllocator.allocate(32)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/allocator/CompositeAllocatorTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.util.*; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | 21 | /** 22 | * Unit tests for the CompositeAllocator. 23 | * 24 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 25 | * @since 2020-06 26 | */ 27 | public class CompositeAllocatorTests { 28 | 29 | public static int PAGE_SIZE = 1024 * 1024 * 4; 30 | 31 | @Test 32 | public void testBasicOperations() { 33 | 34 | CompositeAllocator compositeAllocator = new CompositeAllocator(0, PAGE_SIZE); 35 | 36 | List addresses = fill(compositeAllocator, 8); 37 | assertEquals(PAGE_SIZE / 8, addresses.size()); 38 | 39 | Set uniqAddresses = new HashSet<>(addresses); 40 | assertEquals(PAGE_SIZE / 8, uniqAddresses.size()); 41 | assertFalse(uniqAddresses.contains(-1L)); 42 | 43 | assertEquals(-1, compositeAllocator.allocate(8)); 44 | 45 | empty(compositeAllocator, 8, addresses); 46 | } 47 | 48 | @Test 49 | public void testBackingTooSmall() { 50 | assertThrows(IllegalArgumentException.class, () -> new CompositeAllocator(0, PAGE_SIZE - 1)); 51 | } 52 | 53 | @Test 54 | public void testRequestTooBig() { 55 | CompositeAllocator compositeAllocator = new CompositeAllocator(0, PAGE_SIZE); 56 | assertEquals(-1, compositeAllocator.allocate(PAGE_SIZE + 1)); 57 | 58 | assertThrows(IllegalArgumentException.class, () -> compositeAllocator.free(0, PAGE_SIZE + 1)); 59 | } 60 | 61 | @Test 62 | public void testOddSize() { 63 | CompositeAllocator compositeAllocator = new CompositeAllocator(0, 1024 * 1024 * 4); 64 | long addr = compositeAllocator.allocate(5); 65 | assertNotEquals(-1, addr); 66 | compositeAllocator.free(addr, 5); 67 | } 68 | 69 | private void empty(CompositeAllocator compositeAllocator, int elementSize, List addresses) { 70 | for (Long l : addresses) { 71 | compositeAllocator.free(l, elementSize); 72 | assertTrue(compositeAllocator.isFree(l, elementSize)); 73 | } 74 | } 75 | 76 | private List fill(CompositeAllocator compositeAllocator, int elementSize) { 77 | int n = (int) compositeAllocator.getBackingSize() / elementSize; 78 | List allocatedAddresses = new ArrayList<>(n); 79 | for (int i = 0; i < n; i++) { 80 | Long addr = compositeAllocator.allocate(elementSize); 81 | allocatedAddresses.add(addr); 82 | assertFalse(compositeAllocator.isFree(addr, elementSize)); 83 | } 84 | return allocatedAddresses; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/allocator/LargeMemoryBackedObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import io.mashona.pobj.runtime.MemoryBackedObject; 16 | import io.mashona.pobj.runtime.MemoryOperations; 17 | 18 | /** 19 | * Test case helper that has a large size. 20 | * 21 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 22 | * @since 2020-06 23 | */ 24 | public class LargeMemoryBackedObject implements MemoryBackedObject { 25 | 26 | private MemoryOperations memory; 27 | 28 | public LargeMemoryBackedObject() { 29 | } 30 | 31 | @Override 32 | public void setMemory(MemoryOperations memory) { 33 | this.memory = memory; 34 | } 35 | 36 | @Override 37 | public int size() { 38 | return 1024*1024*10; 39 | } 40 | 41 | @Override 42 | public MemoryOperations getMemory() { 43 | return memory; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/allocator/MemoryHeapTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import io.mashona.pobj.generated.MBOTestEntity; 16 | import org.junit.jupiter.api.AfterEach; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertThrows; 25 | 26 | /** 27 | * Unit tests for the MemoryHeap. 28 | * 29 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 30 | * @since 2020-06 31 | */ 32 | public class MemoryHeapTests { 33 | 34 | private static File TEST_DIR = new File("/mnt/pmem/test"); // TODO System.getenv("PMEM_TEST_DIR")); 35 | 36 | File heapFile = new File(TEST_DIR, "test.heap"); 37 | private MemoryHeap memoryHeap; 38 | 39 | @BeforeEach 40 | public void setUp() throws IOException { 41 | 42 | CompositeAllocator compositeAllocator = new CompositeAllocator(0, CompositeAllocatorTests.PAGE_SIZE); 43 | 44 | if (heapFile.exists()) { 45 | heapFile.delete(); 46 | } 47 | 48 | memoryHeap = new MemoryHeap(heapFile, compositeAllocator.getBackingSize(), compositeAllocator); 49 | } 50 | 51 | @AfterEach 52 | public void tearDown() throws IOException { 53 | memoryHeap.close(); 54 | if (heapFile.exists()) { 55 | heapFile.delete(); 56 | } 57 | } 58 | 59 | @Test 60 | public void testBasicOperations() { 61 | 62 | MBOTestEntity mboTestEntity = memoryHeap.newInstance(MBOTestEntity.class); 63 | 64 | assertEquals(0, mboTestEntity.getMyByte()); 65 | mboTestEntity.setMyByte((byte) 1); 66 | assertEquals(1, mboTestEntity.getMyByte()); 67 | 68 | MBOTestEntity duplicateTestEntity = memoryHeap.attachInstance(MBOTestEntity.class, mboTestEntity.getMemory().getHeapOffset()); 69 | assertEquals(1, duplicateTestEntity.getMyByte()); 70 | 71 | memoryHeap.delete(mboTestEntity); 72 | memoryHeap.delete(duplicateTestEntity); 73 | 74 | assertThrows(IllegalArgumentException.class, () -> memoryHeap.attachInstance(MBOTestEntity.class, mboTestEntity.getMemory().getHeapOffset())); 75 | 76 | assertThrows(IllegalStateException.class, () -> mboTestEntity.setMyByte((byte) 1)); 77 | } 78 | 79 | @Test 80 | public void testOperateOnClosed() throws IOException { 81 | memoryHeap.close(); 82 | assertThrows(IllegalStateException.class, () -> memoryHeap.newInstance(MBOTestEntity.class)); 83 | assertThrows(IllegalStateException.class, () -> memoryHeap.delete(null)); 84 | } 85 | 86 | @Test 87 | public void testBadObjectType() { 88 | assertThrows(RuntimeException.class, () -> memoryHeap.newInstance(BogusMemoryBackedObject.class)); 89 | } 90 | 91 | @Test 92 | public void testNotEnoughSpace() { 93 | assertThrows(RuntimeException.class, () -> memoryHeap.newInstance(LargeMemoryBackedObject.class)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/allocator/RegionBitmapPersistenceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.nio.ByteBuffer; 18 | import java.util.*; 19 | 20 | import static org.junit.jupiter.api.Assertions.*; 21 | 22 | /** 23 | * Unit tests for persistence of RegionBitmaps. 24 | * 25 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 26 | * @since 2020-06 27 | */ 28 | public class RegionBitmapPersistenceTests { 29 | 30 | @Test 31 | public void testPersistable() { 32 | 33 | RegionBitmap regionBitmap = new RegionBitmap(1024, new RegionConfig(8, 552)); 34 | RegionBitmapPersistence regionBitmapPersistence = new RegionBitmapPersistence(); 35 | 36 | Set allocations = new HashSet<>(); 37 | for (int i = 0; i < regionBitmap.getMaxElements(); i++) { 38 | long allocation = regionBitmap.allocate(); 39 | assertFalse(allocations.contains(allocation)); 40 | allocations.add(allocation); 41 | } 42 | 43 | assertEquals(regionBitmap.getMaxElements(), allocations.size()); 44 | 45 | assertEquals(-1, regionBitmap.allocate()); 46 | 47 | ByteBuffer byteBuffer = ByteBuffer.allocate(48); 48 | regionBitmapPersistence.writeInto(regionBitmap, byteBuffer); 49 | byteBuffer.flip(); 50 | 51 | RegionBitmap recoveredRegionBitmap = regionBitmapPersistence.readFrom(byteBuffer); 52 | 53 | assertEquals(regionBitmap.getBackingSize(), recoveredRegionBitmap.getBackingSize()); 54 | assertEquals(regionBitmap.getElementSize(), recoveredRegionBitmap.getElementSize()); 55 | assertEquals(regionBitmap.getMaxElements(), recoveredRegionBitmap.getMaxElements()); 56 | assertEquals(regionBitmap.getNumAvail(), recoveredRegionBitmap.getNumAvail()); 57 | 58 | assertEquals(-1, recoveredRegionBitmap.allocate()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/allocator/RegionBitmapTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.allocator; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.util.*; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | 21 | /** 22 | * Unit tests for the RegionBitmap. 23 | * 24 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 25 | * @since 2020-06 26 | */ 27 | public class RegionBitmapTests { 28 | 29 | @Test 30 | public void testBasicOperations() { 31 | 32 | RegionBitmap regionBitmap = new RegionBitmap(0, new RegionConfig(8, 1024)); 33 | 34 | assertEquals(1024, regionBitmap.getBackingSize()); 35 | assertEquals(8, regionBitmap.getElementSize()); 36 | assertEquals(1024 / 8, regionBitmap.getMaxElements()); 37 | assertEquals(regionBitmap.getMaxElements(), regionBitmap.getNumAvail()); 38 | 39 | Set allocations = new HashSet<>(); 40 | for (int i = 0; i < regionBitmap.getMaxElements(); i++) { 41 | long allocation = regionBitmap.allocate(); 42 | assertTrue(allocations.add(allocation)); 43 | assertFalse(regionBitmap.isFree(allocation)); 44 | } 45 | 46 | assertEquals(regionBitmap.getMaxElements(), allocations.size()); 47 | assertEquals(0, regionBitmap.getNumAvail()); 48 | 49 | assertEquals(-1, regionBitmap.allocate()); 50 | 51 | for (Long l : allocations) { 52 | regionBitmap.free(l); 53 | assertTrue(regionBitmap.isFree(l)); 54 | } 55 | 56 | assertEquals(regionBitmap.getMaxElements(), regionBitmap.getNumAvail()); 57 | } 58 | 59 | @Test 60 | public void testNonMultipleSize() { 61 | RegionBitmap regionBitmap = new RegionBitmap(0, new RegionConfig(8, 1024 - 1)); 62 | assertEquals((1024 / 8) - 1, regionBitmap.getMaxElements()); 63 | } 64 | 65 | @Test 66 | public void testIntegerOverflow() { 67 | assertThrows(IllegalArgumentException.class, 68 | () -> new RegionBitmap(0, new RegionConfig(1, 1L + Integer.MAX_VALUE))); 69 | } 70 | 71 | @Test 72 | public void testInvalidFrees() { 73 | 74 | RegionBitmap regionBitmap = new RegionBitmap(0, new RegionConfig(8, 1024)); 75 | 76 | long addr = regionBitmap.allocate(); 77 | regionBitmap.free(addr); 78 | 79 | assertThrows(IllegalArgumentException.class, 80 | () -> regionBitmap.free(addr)); 81 | 82 | assertThrows(IllegalArgumentException.class, 83 | () -> regionBitmap.free(-1)); 84 | 85 | assertThrows(IllegalArgumentException.class, 86 | () -> regionBitmap.free(1024 + 8)); 87 | } 88 | 89 | @Test 90 | public void testComparator() { 91 | RegionBitmap regionBitmapA = new RegionBitmap(0, new RegionConfig(8, 1024)); 92 | RegionBitmap regionBitmapB = new RegionBitmap(1024, new RegionConfig(8, 1024)); 93 | RegionBitmap regionBitmapC = new RegionBitmap(2048, new RegionConfig(8, 1024)); 94 | 95 | assertEquals(-1, regionBitmapA.compareTo(regionBitmapB.baseAddress)); 96 | assertEquals(0, regionBitmapB.compareTo(regionBitmapB.baseAddress)); 97 | assertEquals(1, regionBitmapC.compareTo(regionBitmapB.baseAddress)); 98 | 99 | List sortedList = List.of(regionBitmapA, regionBitmapB, regionBitmapC); 100 | 101 | List list = new ArrayList<>(List.of(regionBitmapB, regionBitmapC, regionBitmapA)); 102 | 103 | assertNotEquals(sortedList, list); 104 | Collections.sort(list, RegionBitmap.COMPARATOR); 105 | assertEquals(sortedList, list); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/generated/PointImpl.java: -------------------------------------------------------------------------------- 1 | package io.mashona.pobj.generated; 2 | 3 | import io.mashona.pobj.runtime.MemoryOperations; 4 | import io.mashona.pobj.runtime.MemoryBackedObject; 5 | 6 | // automatically generated class. 7 | public class PointImpl implements MemoryBackedObject { 8 | 9 | protected static final int X_OFFSET = 0; 10 | protected static final int X_STORESIZE = 4; 11 | protected static final int Y_OFFSET = 4; 12 | protected static final int Y_STORESIZE = 4; 13 | protected static final int TOTAL_STORESIZE = 8; 14 | 15 | protected MemoryOperations memory; 16 | 17 | @Override 18 | public int size() { 19 | return TOTAL_STORESIZE; 20 | } 21 | 22 | @Override 23 | public void setMemory(MemoryOperations memory) { 24 | this.memory = memory; 25 | } 26 | 27 | @Override 28 | public MemoryOperations getMemory() { 29 | return memory; 30 | } 31 | 32 | public int getX() { 33 | return memory.getInt(X_OFFSET); 34 | } 35 | 36 | public void setX(int x) { 37 | memory.setInt(X_OFFSET, x); 38 | } 39 | 40 | public int getY() { 41 | return memory.getInt(Y_OFFSET); 42 | } 43 | 44 | public void setY(int y) { 45 | memory.setInt(Y_OFFSET, y); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/transaction/CrashRecoveryTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.generated.PointImpl; 16 | import org.junit.jupiter.api.AfterEach; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertThrows; 25 | 26 | /** 27 | * Unit tests for crash recovery of persistent transactions 28 | * 29 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 30 | * @since 2020-07 31 | */ 32 | public class CrashRecoveryTests { 33 | 34 | private static File TEST_DIR = new File("/mnt/pmem/test"); // TODO System.getenv("PMEM_TEST_DIR")); 35 | 36 | private PersistentTransactionManager persistentTransactionManager; 37 | private TransactionalCompositeAllocator compositeAllocator; 38 | private TransactionalMemoryHeap transactionalMemoryHeap; 39 | 40 | private static File storeFile = new File(TEST_DIR, "transaction_store"); 41 | private static File heapFile = new File(TEST_DIR, "heap"); 42 | private TransactionStore transactionStore; 43 | 44 | protected PersistentTransactionManager getTransactionManager() { 45 | return new PersistentTransactionManager(transactionStore); 46 | } 47 | 48 | @BeforeEach 49 | public void setUp() throws IOException { 50 | heapFile.delete(); 51 | storeFile.delete(); 52 | init(); 53 | } 54 | 55 | @AfterEach 56 | public void tearDown() throws IOException { 57 | transactionalMemoryHeap.close(); 58 | heapFile.delete(); 59 | transactionStore.close(); 60 | storeFile.delete(); 61 | } 62 | 63 | private void init() throws IOException { 64 | transactionStore = new TransactionStore(storeFile, 1024*1024); 65 | persistentTransactionManager = getTransactionManager(); 66 | compositeAllocator = new TransactionalCompositeAllocator(0, 1024 * 1024 * 4, persistentTransactionManager); 67 | transactionalMemoryHeap = new TransactionalMemoryHeap( 68 | heapFile, compositeAllocator.getBackingSize(), compositeAllocator, persistentTransactionManager); 69 | } 70 | 71 | private void simulateCrash() throws IOException { 72 | transactionStore.close(); 73 | init(); 74 | } 75 | 76 | @Test 77 | public void testRecoverCommittedTransaction() throws IOException { 78 | 79 | persistentTransactionManager.begin(); 80 | 81 | PointImpl pointA = transactionalMemoryHeap.newInstance(PointImpl.class); 82 | pointA.setX(100); 83 | pointA.setY(200); 84 | 85 | persistentTransactionManager.commit(); 86 | 87 | long addr = pointA.getMemory().getHeapOffset(); 88 | 89 | simulateCrash(); 90 | 91 | persistentTransactionManager.begin(); 92 | assertThrows(IllegalArgumentException.class, () -> transactionalMemoryHeap.attachInstance(PointImpl.class, addr)); 93 | persistentTransactionManager.rollback(); 94 | 95 | persistentTransactionManager.recover(transactionalMemoryHeap); 96 | 97 | persistentTransactionManager.begin(); 98 | PointImpl pointB = transactionalMemoryHeap.attachInstance(PointImpl.class, addr); 99 | 100 | assertEquals(100, pointB.getX()); 101 | assertEquals(200, pointB.getY()); 102 | 103 | pointA.getMemory().delete(); 104 | pointB.getMemory().delete(); 105 | persistentTransactionManager.commit(); 106 | } 107 | 108 | @Test 109 | public void testRecoverRolledbackTransaction() throws IOException { 110 | 111 | persistentTransactionManager.begin(); 112 | PointImpl pointA = transactionalMemoryHeap.newInstance(PointImpl.class); 113 | pointA.setX(100); 114 | persistentTransactionManager.commit(); 115 | 116 | persistentTransactionManager.begin(); 117 | pointA.setX(101); 118 | persistentTransactionManager.rollback(); 119 | 120 | long addr = pointA.getMemory().getHeapOffset(); 121 | 122 | simulateCrash(); 123 | persistentTransactionManager.recover(transactionalMemoryHeap); 124 | 125 | persistentTransactionManager.begin(); 126 | PointImpl pointB = transactionalMemoryHeap.attachInstance(PointImpl.class, addr); 127 | 128 | assertEquals(100, pointB.getX()); 129 | 130 | pointA.getMemory().delete(); 131 | pointB.getMemory().delete(); 132 | persistentTransactionManager.commit(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/transaction/PersistentTransactionWiringTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.transaction.events.CreateEvent; 16 | import io.mashona.pobj.transaction.events.DeleteEvent; 17 | import io.mashona.pobj.transaction.events.TransactionEvent; 18 | import org.junit.jupiter.api.AfterEach; 19 | import org.junit.jupiter.api.BeforeEach; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertFalse; 28 | 29 | /** 30 | * Unit tests for integrated behaviour of the transaction system classes. 31 | * This extends the volatile (in-memory) tests with coverage of the persistence (logging) mechanism. 32 | * 33 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 34 | * @since 2020-07 35 | */ 36 | public class PersistentTransactionWiringTests extends TransactionWiringTests { 37 | 38 | private static File TEST_DIR = new File("/mnt/pmem/test"); // TODO System.getenv("PMEM_TEST_DIR")); 39 | 40 | private static File storeFile = new File(TEST_DIR, "transaction_store"); 41 | private TransactionStore transactionStore; 42 | 43 | @BeforeEach 44 | public void setUp() throws IOException { 45 | storeFile.delete(); 46 | transactionStore = new TransactionStore(storeFile, 1024 * 1024); 47 | super.setUp(); 48 | } 49 | 50 | @AfterEach 51 | public void tearDown() throws IOException { 52 | super.tearDown(); 53 | transactionStore.close(); 54 | storeFile.delete(); 55 | } 56 | 57 | @Override 58 | protected TransactionManager getTransactionManager() { 59 | return new PersistentTransactionManager(transactionStore); 60 | } 61 | 62 | @Override 63 | protected void checkTransactionLog() { 64 | 65 | VolatileTransaction volatileTransaction = transactionManager.getCurrent(); 66 | List volatileTransactionEventsList = volatileTransaction.events; 67 | 68 | List transactionList = transactionStore.read(); 69 | PersistentTransaction latestLoggedTransaction = transactionList.get(transactionList.size() - 1); 70 | 71 | Iterator loggedTransactionEventsIterator = latestLoggedTransaction.events.iterator(); 72 | 73 | for (TransactionEvent expectedEvent : volatileTransactionEventsList) { 74 | if (expectedEvent instanceof CreateEvent || expectedEvent instanceof DeleteEvent) { 75 | continue; // these are volatile and not persisted 76 | } 77 | TransactionEvent actualEvent = loggedTransactionEventsIterator.next(); 78 | assertEquals(expectedEvent, actualEvent); 79 | } 80 | 81 | assertFalse(loggedTransactionEventsIterator.hasNext()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/transaction/TransactionWiringTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction; 14 | 15 | import io.mashona.pobj.generated.PointImpl; 16 | import com.redhat.mashona.pobj.transaction.events.*; 17 | import io.mashona.pobj.transaction.events.*; 18 | import org.junit.jupiter.api.AfterEach; 19 | import org.junit.jupiter.api.BeforeEach; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.List; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * Unit tests for integrated behaviour of the transaction system classes. 31 | * 32 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 33 | * @since 2020-07 34 | */ 35 | public class TransactionWiringTests { 36 | 37 | protected TransactionManager transactionManager; 38 | protected TransactionalCompositeAllocator compositeAllocator; 39 | protected TransactionalMemoryHeap transactionalMemoryHeap; 40 | 41 | protected TransactionManager getTransactionManager() { 42 | return new TransactionManager(); 43 | } 44 | 45 | protected void checkTransactionLog() { 46 | // null-op here in the volatile version - extension point for PersistentTransactionWiringTests 47 | } 48 | 49 | @BeforeEach 50 | public void setUp() throws IOException { 51 | transactionManager = getTransactionManager(); 52 | compositeAllocator = new TransactionalCompositeAllocator(0, 1024 * 1024 * 4, transactionManager); 53 | transactionalMemoryHeap = new TransactionalMemoryHeap( 54 | new File("/mnt/pmem/test/heap"), compositeAllocator.getBackingSize(), compositeAllocator, transactionManager); 55 | } 56 | 57 | @AfterEach 58 | public void tearDown() throws IOException { 59 | transactionalMemoryHeap.close(); 60 | } 61 | 62 | @Test 63 | public void testAllocatingAndMutating() throws IOException { 64 | 65 | transactionManager.begin(); 66 | 67 | PointImpl pointA = transactionalMemoryHeap.newInstance(PointImpl.class); 68 | pointA.setX(100); 69 | pointA.setY(200); 70 | 71 | PointImpl pointB = transactionalMemoryHeap.newInstance(PointImpl.class); 72 | transactionalMemoryHeap.delete(pointB); 73 | 74 | assertThrows(IllegalStateException.class, () -> pointB.getX()); 75 | assertThrows(IllegalStateException.class, () -> pointB.setX(300)); 76 | 77 | VolatileTransaction volatileTransaction = transactionManager.getCurrent(); 78 | 79 | transactionManager.commit(); 80 | 81 | assertEquals(100, pointA.getX()); 82 | assertEquals(200, pointA.getY()); 83 | 84 | assertThrows(IllegalStateException.class, () -> pointB.getX()); 85 | 86 | List entries = volatileTransaction.events; 87 | assertEquals(9, entries.size()); 88 | assertEquals(MallocEvent.class, entries.get(0).getClass()); // alloc pointA 89 | assertEquals(CreateEvent.class, entries.get(1).getClass()); // create point A 90 | assertEquals(BeforeWriteEvent.class, entries.get(2).getClass()); // pointA.setX 91 | assertEquals(BeforeWriteEvent.class, entries.get(3).getClass()); // pointA.setY 92 | assertEquals(MallocEvent.class, entries.get(4).getClass()); // alloc pointB 93 | assertEquals(CreateEvent.class, entries.get(5).getClass()); // create pointB 94 | assertEquals(DeallocateEvent.class, entries.get(6).getClass()); // deallocate pointB 95 | assertEquals(DeleteEvent.class, entries.get(7).getClass()); // delete pointB 96 | assertEquals(OutcomeEvent.class, entries.get(8).getClass()); // commit 97 | 98 | checkTransactionLog(); 99 | 100 | transactionManager.begin(); 101 | pointA.getMemory().delete(); 102 | transactionManager.commit(); 103 | } 104 | 105 | @Test 106 | public void testRollback() throws IOException { 107 | 108 | transactionManager.begin(); 109 | 110 | PointImpl pointA = transactionalMemoryHeap.newInstance(PointImpl.class); 111 | pointA.setX(100); 112 | 113 | PointImpl pointB = transactionalMemoryHeap.newInstance(PointImpl.class); 114 | pointB.setX(200); 115 | 116 | transactionManager.commit(); 117 | 118 | assertEquals(100, pointA.getX()); 119 | assertEquals(200, pointB.getX()); 120 | 121 | transactionManager.begin(); 122 | 123 | pointA.setX(101); 124 | 125 | transactionalMemoryHeap.delete(pointB); 126 | 127 | PointImpl pointC = transactionalMemoryHeap.newInstance(PointImpl.class); 128 | pointC.setX(300); 129 | 130 | VolatileTransaction volatileTransaction = transactionManager.getCurrent(); 131 | 132 | transactionManager.rollback(); 133 | 134 | assertEquals(100, pointA.getX()); 135 | assertEquals(200, pointB.getX()); 136 | assertThrows(IllegalStateException.class, () -> pointC.getX()); 137 | 138 | List entries = volatileTransaction.events; 139 | assertEquals(7, entries.size()); 140 | assertEquals(BeforeWriteEvent.class, entries.get(0).getClass()); // pointA.setX 141 | assertEquals(DeallocateEvent.class, entries.get(1).getClass()); // deallocate pointB 142 | assertEquals(DeleteEvent.class, entries.get(2).getClass()); // delete pointB 143 | assertEquals(MallocEvent.class, entries.get(3).getClass()); // alloc pointC 144 | assertEquals(CreateEvent.class, entries.get(4).getClass()); // create pointC 145 | assertEquals(BeforeWriteEvent.class, entries.get(5).getClass()); // pointC.setX 146 | assertEquals(OutcomeEvent.class, entries.get(6).getClass()); // rollback 147 | 148 | checkTransactionLog(); 149 | 150 | transactionManager.begin(); 151 | pointA.getMemory().delete(); 152 | pointB.getMemory().delete(); 153 | pointC.getMemory().delete(); 154 | transactionManager.commit(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/transaction/events/TransactionEventPersistenceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.nio.ByteBuffer; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | /** 22 | * Unit tests for various persistence helper classes responsible for (de)serializing LoggableTransactionEvent types. 23 | * 24 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 25 | * @since 2020-07 26 | */ 27 | public class TransactionEventPersistenceTests { 28 | 29 | @Test 30 | public void testMallocEventPersistence() { 31 | MallocEvent before = new MallocEvent(100, 200, true); 32 | MallocEventPersistence persistence = new MallocEventPersistence(); 33 | 34 | ByteBuffer byteBuffer = ByteBuffer.allocate(50); 35 | persistence.writeInto(before, byteBuffer); 36 | byteBuffer.rewind(); 37 | 38 | assertEquals(persistence.getFormatId(), byteBuffer.getLong()); 39 | MallocEvent after = persistence.readFrom(byteBuffer); 40 | 41 | assertEquals(before.getOffset(), after.getOffset()); 42 | assertEquals(before.getSize(), after.getSize()); 43 | assertEquals(before.isForInternalUse(), after.isForInternalUse()); 44 | } 45 | 46 | @Test 47 | public void testDeleteEventPersistence() { 48 | DeallocateEvent before = new DeallocateEvent(100, 200); 49 | DeallocateEventPersistence persistence = new DeallocateEventPersistence(); 50 | 51 | ByteBuffer byteBuffer = ByteBuffer.allocate(50); 52 | persistence.writeInto(before, byteBuffer); 53 | byteBuffer.rewind(); 54 | 55 | assertEquals(persistence.getFormatId(), byteBuffer.getLong()); 56 | DeallocateEvent after = persistence.readFrom(byteBuffer); 57 | 58 | assertEquals(before.getOffset(), after.getOffset()); 59 | assertEquals(before.getSize(), after.getSize()); 60 | } 61 | 62 | @Test 63 | public void testBeforeWriteEventPersistence() { 64 | ByteBuffer payloadBuffer = ByteBuffer.allocate(16); 65 | payloadBuffer.putLong(Long.MIN_VALUE); 66 | payloadBuffer.putLong(Long.MAX_VALUE); 67 | payloadBuffer.rewind(); 68 | BeforeWriteEvent before = new BeforeWriteEvent(100, 16, payloadBuffer); 69 | BeforeWriteEventPersistence persistence = new BeforeWriteEventPersistence(); 70 | 71 | ByteBuffer persistenceBuffer = ByteBuffer.allocate(50); 72 | persistence.writeInto(before, persistenceBuffer); 73 | persistenceBuffer.rewind(); 74 | 75 | assertEquals(persistence.getFormatId(), persistenceBuffer.getLong()); 76 | BeforeWriteEvent after = persistence.readFrom(persistenceBuffer); 77 | 78 | assertEquals(before.getOffset(), after.getOffset()); 79 | assertEquals(before.getSize(), after.getSize()); 80 | 81 | ByteBuffer afterPayloadBuffer = after.getByteBuffer(); 82 | assertEquals(Long.MIN_VALUE, afterPayloadBuffer.getLong()); 83 | assertEquals(Long.MAX_VALUE, afterPayloadBuffer.getLong()); 84 | } 85 | 86 | @Test 87 | public void testOutcomeEventPersistence() { 88 | OutcomeEvent before = new OutcomeEvent(true); 89 | OutcomeEventPersistence persistence = new OutcomeEventPersistence(); 90 | 91 | ByteBuffer byteBuffer = ByteBuffer.allocate(50); 92 | persistence.writeInto(before, byteBuffer); 93 | byteBuffer.rewind(); 94 | 95 | assertEquals(persistence.getFormatId(), byteBuffer.getLong()); 96 | OutcomeEvent after = persistence.readFrom(byteBuffer); 97 | 98 | assertEquals(before.isCommit(), after.isCommit()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pobj/src/test/java/io/mashona/pobj/transaction/events/TransactionEventTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Red Hat, Inc. and/or its affiliates. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | package io.mashona.pobj.transaction.events; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.nio.ByteBuffer; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | 21 | /** 22 | * Unit tests for various LoggableTransactionEvent types. 23 | * 24 | * @author Jonathan Halliday (jonathan.halliday@redhat.com) 25 | * @since 2020-07 26 | */ 27 | public class TransactionEventTests { 28 | 29 | @Test 30 | public void testMallocEvent() { 31 | MallocEvent mallocEvent = new MallocEvent(100, 200, true); 32 | assertEquals(100, mallocEvent.getOffset()); 33 | assertEquals(200, mallocEvent.getSize()); 34 | assertTrue(mallocEvent.isForInternalUse()); 35 | } 36 | 37 | @Test 38 | public void testCreateEvent() { 39 | CreateEvent createEvent = new CreateEvent(null); 40 | assertNull(createEvent.getMemory()); 41 | } 42 | 43 | @Test 44 | public void testDeallocateEvent() { 45 | DeallocateEvent deallocateEvent = new DeallocateEvent(100, 200); 46 | assertEquals(100, deallocateEvent.getOffset()); 47 | assertEquals(200, deallocateEvent.getSize()); 48 | } 49 | 50 | @Test 51 | public void testDeleteEvent() { 52 | DeleteEvent deleteEvent = new DeleteEvent(null); 53 | assertNull(deleteEvent.getMemory()); 54 | } 55 | 56 | @Test 57 | public void testBeforeWriteEvent() { 58 | ByteBuffer byteBuffer = ByteBuffer.allocate(16); 59 | byteBuffer.putLong(Long.MIN_VALUE); 60 | byteBuffer.putLong(Long.MIN_VALUE); 61 | BeforeWriteEvent beforeWriteEvent = new BeforeWriteEvent(100, 16, byteBuffer); 62 | assertEquals(100, beforeWriteEvent.getOffset()); 63 | assertEquals(16, beforeWriteEvent.getSize()); 64 | assertEquals(byteBuffer, beforeWriteEvent.getByteBuffer()); 65 | } 66 | 67 | @Test 68 | public void testOutcomeEvent() { 69 | OutcomeEvent outcomeEvent = new OutcomeEvent(true); 70 | assertTrue(outcomeEvent.isCommit()); 71 | } 72 | } 73 | --------------------------------------------------------------------------------