├── mmap_wrapper.h ├── README.md ├── mmap_wrapper.cpp └── disruptor.h /mmap_wrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef mmap_h 2 | #define mmap_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct MMap_Info { 14 | int fd; 15 | void *location; 16 | const char *name; 17 | int size; 18 | }; 19 | 20 | MMap_Info *init_mmap(const char *name, int size); 21 | MMap_Info *open_mmap(const char *name, int size); 22 | void delete_mmap(MMap_Info *info); 23 | void close_mmap(MMap_Info *info); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello World LMAX Disruptor (C++ < 200 LOC) 2 | 3 | ## TLDR 4 | 5 | This is an implementation of the LMAX disruptor (commonly used in major stock exchanges like NASDAQ) in C++ < 200 LOC. An LMAX disruptor is a ring buffer on top of a shared memory buffer with one process writing to the ring buffer and one or more processes consuming from it. 6 | 7 | This allows for insanely fast communication between processes at the cost of durability and increased memory usage. 8 | 9 | For now check out https://github.com/sneilan/stock-exchange for an example of it in action. 10 | 11 | I'm also open to employment opportunities. Email me at sean@seanneilan.com! 12 | -------------------------------------------------------------------------------- /mmap_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "mmap_wrapper.h" 2 | 3 | MMap_Info *init_mmap(const char *name, int size) { 4 | int fd = shm_open(name, O_CREAT | O_RDWR, 0777); 5 | 6 | if (fd == -1) { 7 | throw std::runtime_error( 8 | "Could not open file descriptor to mmap in controller."); 9 | } 10 | 11 | if (ftruncate(fd, size) == -1) { 12 | throw std::runtime_error("Could not resize mmap in controller"); 13 | } 14 | 15 | void *location = 16 | mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 17 | 18 | if (location == MAP_FAILED) { 19 | throw std::runtime_error("Could not map mmap region to controller."); 20 | } 21 | 22 | new (location) char[size]; 23 | 24 | memset(location, 0, size); 25 | 26 | // This is allocated w/o a destructor as it is assumed 27 | // program will instantiate an mmap once. 28 | MMap_Info *info = new MMap_Info(); 29 | 30 | info->location = location; 31 | info->fd = fd; 32 | info->name = name; 33 | info->size = size; 34 | 35 | return info; 36 | }; 37 | 38 | void delete_mmap(MMap_Info *info) { 39 | munmap(info->location, info->size); 40 | shm_unlink(info->name); 41 | } 42 | 43 | MMap_Info *open_mmap(const char *name, int size) { 44 | int fd = shm_open(name, O_RDWR, 0777); 45 | 46 | if (fd == -1) { 47 | throw std::runtime_error( 48 | "Could not open file descriptor to mmap in client."); 49 | } 50 | 51 | void *location = 52 | mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 53 | 54 | if (location == MAP_FAILED) { 55 | throw std::runtime_error("Could not map mmap region to client."); 56 | } 57 | 58 | // This is allocated w/o a destructor as it is assumed 59 | // program will instantiate an mmap once. 60 | MMap_Info *info = new MMap_Info(); 61 | info->location = location; 62 | info->fd = fd; 63 | info->name = name; 64 | info->size = size; 65 | 66 | return info; 67 | } 68 | 69 | void close_mmap(MMap_Info *info) { 70 | munmap(info->location, info->size); 71 | close(info->fd); 72 | } 73 | -------------------------------------------------------------------------------- /disruptor.h: -------------------------------------------------------------------------------- 1 | // Implementation of ring buffer for processes to communicate with each other 2 | // See https://martinfowler.com/articles/lmax.html 3 | // 4 | // Basic concept is producer should never produce more than consumer can consume. 5 | // and Consumer should never consume more than consumer can produce. 6 | // Otherwise it's not a ring buffer anymore it's a snake eating it's own tail. 7 | // 8 | // The consumer can consume up until and including the producer position 9 | // But the producer position can only produce up until the consumer position. 10 | 11 | #include "mmap_wrapper.h" 12 | #include 13 | 14 | template struct SharedData { 15 | int producer_position; 16 | int consumer_position; 17 | T *entities; 18 | }; 19 | 20 | template class Disruptor { 21 | protected: 22 | MMap_Info *mmap_info; 23 | SharedData *shared_mem_region; 24 | int get_mmap_size(); 25 | int slots; 26 | }; 27 | 28 | template class Producer : public Disruptor { 29 | public: 30 | Producer(int slots, const char *mmap_name); 31 | ~Producer() throw(); 32 | void cleanup(); 33 | // This creates a copy on function call for simplicity. 34 | // Later on use pointers to improve performance. 35 | bool put(T item); 36 | }; 37 | 38 | template class Consumer : public Disruptor { 39 | public: 40 | // producer / consumer behavior is totally separate but 41 | // we have to specify slots twice. 42 | // For now this is fine because producer and consumer are 43 | // created in separate processes anyway. 44 | Consumer(int slots, const char *mmap_name); 45 | ~Consumer() throw(); 46 | T *get(); 47 | void cleanup(); 48 | }; 49 | 50 | template T *Consumer::get() { 51 | SPDLOG_DEBUG("{} Producer/Consumer is {}/{}", this->mmap_info->name, 52 | this->shared_mem_region->producer_position, 53 | this->shared_mem_region->consumer_position); 54 | 55 | // Consumer can consume up until the producer position. Producer is not allowed to produce > consumer position - 1 56 | // So producers next position it will write to is it's current position and the last position it wrote to is 57 | // position - 1. Then consumer can consume only up to producer position - 1. It's a real mind bender but it works. 58 | if (this->shared_mem_region->consumer_position == this->shared_mem_region->producer_position) { 59 | return nullptr; 60 | } 61 | 62 | T *item = &this->shared_mem_region 63 | ->entities[this->shared_mem_region->consumer_position]; 64 | this->shared_mem_region->consumer_position++; 65 | this->shared_mem_region->consumer_position %= this->slots; 66 | 67 | return item; 68 | } 69 | 70 | template int Disruptor::get_mmap_size() { 71 | return sizeof(SharedData) + sizeof(T) * slots; 72 | } 73 | 74 | template void Producer::cleanup() { 75 | delete_mmap(this->mmap_info); 76 | } 77 | 78 | template Producer::~Producer() throw() { cleanup(); } 79 | 80 | template Producer::Producer(int slots, const char *mmap_name) { 81 | this->slots = slots; 82 | this->mmap_info = init_mmap(mmap_name, this->get_mmap_size()); 83 | this->shared_mem_region = (SharedData *)this->mmap_info->location; 84 | 85 | this->shared_mem_region->entities = reinterpret_cast( 86 | (char *)this->shared_mem_region + sizeof(SharedData)); 87 | 88 | // producer always starts ahead of consumer 89 | this->shared_mem_region->producer_position = 0; 90 | } 91 | 92 | template bool Producer::put(T item) { 93 | SPDLOG_DEBUG("{} Producer/Consumer is {}/{}", this->mmap_info->name, 94 | this->shared_mem_region->producer_position, 95 | this->shared_mem_region->consumer_position); 96 | int next_producer_position = (this->shared_mem_region->producer_position+1) % this->slots; 97 | 98 | // Producer cannot produce spots that the consumer cannot read without looping around. 99 | // producer spot must be < consumer spot. 100 | if (next_producer_position == this->shared_mem_region->consumer_position) { 101 | SPDLOG_ERROR("{} Producer overflow!", this->mmap_info->name); 102 | return false; 103 | } 104 | 105 | this->shared_mem_region 106 | ->entities[this->shared_mem_region->producer_position] = item; 107 | 108 | this->shared_mem_region->producer_position++; 109 | this->shared_mem_region->producer_position %= this->slots; 110 | 111 | return true; 112 | } 113 | 114 | template Consumer::Consumer(int slots, const char *mmap_name) { 115 | this->slots = slots; 116 | this->mmap_info = open_mmap(mmap_name, this->get_mmap_size()); 117 | this->shared_mem_region = (SharedData *)this->mmap_info->location; 118 | this->shared_mem_region->consumer_position = 0; 119 | } 120 | 121 | template Consumer::~Consumer() throw() { cleanup(); } 122 | 123 | template void Consumer::cleanup() { 124 | close_mmap(this->mmap_info); 125 | } 126 | --------------------------------------------------------------------------------