Line data Source code
1 : #ifndef HEADER_fd_src_waltz_mib_fd_dbl_buf_h 2 : #define HEADER_fd_src_waltz_mib_fd_dbl_buf_h 3 : 4 : /* fd_dbl_buf.h provides a concurrent lock-free double buffer. A double 5 : buffer contains two buffers that take turns holding a message for 6 : consumers and receiving a new message by a producer. 7 : 8 : Supports a single producer thread and an arbitrary number of consumer 9 : threads. Optimized for rare updates and frequent polling (e.g. config). 10 : Use an fd_tango mcache/dcache pair if you need frequent updates. 11 : 12 : Currently assumes a memory model that preserves store order across 13 : threads (e.g. x86-TSO). Does not use atomics or hardware fences. */ 14 : 15 : #include "../../util/bits/fd_bits.h" 16 : #if FD_HAS_SSE 17 : #include <emmintrin.h> 18 : #endif 19 : 20 : /* FIXME COULD ALLOW FOR IN-PLACE READS WITH PODs BY ADDING A MSG ALIGN ARGUMENT */ 21 : 22 : /* fd_dbl_buf_t is the header of a dbl_buf object. May not be locally 23 : declared. */ 24 : 25 : union __attribute__((aligned(16UL))) fd_dbl_buf { 26 : 27 : struct { 28 : ulong magic; /* ==FD_DBL_BUF_MAGIC */ 29 : ulong mtu; 30 : ulong buf0; /* offset to first buffer from beginning of struct */ 31 : ulong buf1; /* — " — second — " — */ 32 : ulong seq; /* latest msg seq no */ 33 : ulong sz; /* latest msg size */ 34 : ulong pad[2]; 35 : /* objects follow here */ 36 : }; 37 : 38 : # if FD_HAS_SSE 39 : struct { 40 : __m128i magic_mtu; 41 : __m128i buf0_buf1; 42 : __m128i seq_sz; 43 : __m128i pad2; 44 : }; 45 : # endif 46 : 47 : }; 48 : 49 : typedef union fd_dbl_buf fd_dbl_buf_t; 50 : 51 0 : #define FD_DBL_BUF_MAGIC (0xa6c6f85d431c03ceUL) /* random */ 52 : 53 3 : #define FD_DBL_BUF_ALIGN (16UL) 54 : #define FD_DBL_BUF_FOOTPRINT(mtu) \ 55 3 : FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT, \ 56 3 : FD_DBL_BUF_ALIGN, sizeof(fd_dbl_buf_t) ), \ 57 3 : FD_DBL_BUF_ALIGN, FD_ULONG_ALIGN_UP( mtu, FD_DBL_BUF_ALIGN )<<1UL ), \ 58 3 : FD_DBL_BUF_ALIGN ) 59 : 60 : FD_PROTOTYPES_BEGIN 61 : 62 : /* fd_dbl_buf_{align,footprint} describe the memory region of a double 63 : buffer. mtu is the largest possible message size. */ 64 : 65 : ulong 66 : fd_dbl_buf_align( void ); 67 : 68 : ulong 69 : fd_dbl_buf_footprint( ulong mtu ); 70 : 71 : /* fd_dbl_buf_new formats a memory region for use as a double buffer. 72 : shmem points to the memory region matching fd_dbl_buf_{align,footprint}. 73 : Initially, the active object of the double buffer will have sequence 74 : number seq0 and zero byte size. */ 75 : 76 : void * 77 : fd_dbl_buf_new( void * shmem, 78 : ulong mtu, 79 : ulong seq0 ); 80 : 81 : fd_dbl_buf_t * 82 : fd_dbl_buf_join( void * shbuf ); 83 : 84 : void * 85 : fd_dbl_buf_leave( fd_dbl_buf_t * buf ); 86 : 87 : /* fd_dbl_buf_delete unformats the memory region backing a dbl_buf and 88 : releases ownership back to the caller. Returns shbuf. */ 89 : 90 : void * 91 : fd_dbl_buf_delete( void * shbuf ); 92 : 93 : /* fd_dbl_buf_obj_mtu returns the max message size a dbl_buf can store. */ 94 : 95 : static inline ulong 96 0 : fd_dbl_buf_obj_mtu( fd_dbl_buf_t * buf ) { 97 0 : return buf->mtu; 98 0 : } 99 : 100 : /* fd_dbl_buf_seq_query peeks the current sequence number. */ 101 : 102 : static inline ulong 103 0 : fd_dbl_buf_seq_query( fd_dbl_buf_t * buf ) { 104 0 : FD_COMPILER_MFENCE(); 105 0 : ulong seq = FD_VOLATILE_CONST( buf->seq ); 106 0 : FD_COMPILER_MFENCE(); 107 0 : return seq; 108 0 : } 109 : 110 : /* fd_dbl_buf_slot returns a pointer to the buffer for the given sequence 111 : number. */ 112 : 113 : FD_FN_PURE static inline void * 114 : fd_dbl_buf_slot( fd_dbl_buf_t * buf, 115 0 : ulong seq ) { 116 0 : return (seq&1) ? ((char *)buf)+buf->buf1 : ((char *)buf)+buf->buf0; 117 0 : } 118 : 119 : /* fd_dbl_buf_insert appends a message to the double buffer. 120 : 121 : Note: It is NOT safe to call this function from multiple threads. */ 122 : 123 : void 124 : fd_dbl_buf_insert( fd_dbl_buf_t * buf, 125 : void const * msg, 126 : ulong sz ); 127 : 128 : /* fd_dbl_buf_try_read does a speculative read the most recent message 129 : (from the caller's POV). The read may be overrun by a writer. out 130 : points to a buffer of fd_dbl_buf_obj_mtu(buf) bytes. opt_seqp points to 131 : a ulong or NULL. 132 : 133 : On success: 134 : - returns the size of the message read 135 : - a copy of the message is stored at out 136 : - *opt_seqp is set to the msg sequence number (if non-NULL) 137 : 138 : On failure (due to overrun): 139 : - returns ULONG_MAX 140 : - out buffer is clobbered 141 : - *opt_seq is clobbered (if non-NULL) */ 142 : 143 : static inline ulong 144 : fd_dbl_buf_try_read( fd_dbl_buf_t * buf, 145 : void * out, 146 0 : ulong * opt_seqp ) { 147 0 : ulong seq = fd_dbl_buf_seq_query( buf ); 148 0 : void * src = fd_dbl_buf_slot( buf, seq ); 149 0 : ulong sz = FD_VOLATILE_CONST( buf->sz ); 150 0 : fd_memcpy( out, src, sz ); 151 0 : if( FD_UNLIKELY( seq!=fd_dbl_buf_seq_query( buf ) ) ) return ULONG_MAX; 152 0 : fd_ulong_store_if( !!opt_seqp, opt_seqp, seq ); 153 0 : return sz; 154 0 : } 155 : 156 : /* fd_dbl_buf_read does a blocking */ 157 : 158 : ulong 159 : fd_dbl_buf_read( fd_dbl_buf_t * buf, 160 : void * obj, 161 : ulong * opt_seqp ); 162 : 163 : FD_PROTOTYPES_END 164 : 165 : #endif /* HEADER_fd_src_waltz_mib_fd_dbl_buf_h */