88 */
99
1010#pragma once
11- #include < deque >
11+ #include < map >
1212#include < memory>
1313#include < mutex>
1414#include < unordered_map>
@@ -75,17 +75,19 @@ namespace csv {
7575
7676 this ->buffers = std::move (other.buffers );
7777 _current_buffer_size = other._current_buffer_size ;
78+ _current_block = other._current_block ;
7879
7980 // Recalculate _back pointer to point into OUR buffers, not the moved-from ones
8081 if (!this ->buffers .empty ()) {
81- _back = this ->buffers . back () .get () + _current_buffer_size;
82+ _back = this ->buffers [_current_block] .get () + _current_buffer_size;
8283 } else {
8384 _back = nullptr ;
8485 }
8586
8687 // Invalidate moved-from state to prevent use-after-move bugs
8788 other._back = nullptr ;
8889 other._current_buffer_size = 0 ;
90+ other._current_block = 0 ;
8991 }
9092
9193 template <class ... Args>
@@ -99,33 +101,37 @@ namespace csv {
99101 }
100102
101103 size_t size () const noexcept {
102- return this ->_current_buffer_size + (( this -> buffers . size () - 1 ) * this ->_single_buffer_capacity );
104+ return this ->_current_buffer_size + (_current_block * this ->_single_buffer_capacity );
103105 }
104106
105107 RawCSVField& operator [](size_t n) const ;
106108
107109 private:
108110 const size_t _single_buffer_capacity;
109111
110- /* * Deque of pointers to RawCSVField arrays.
112+ /* * Map of block indices to RawCSVField arrays.
111113 *
112- * std::deque is critical for thread safety: unlike std::vector, it never reallocates
113- * existing elements when expanding. This prevents a race condition where:
114- * 1. Reading thread accesses a RawCSVField via pointer
115- * 2. Parsing thread pushes to at-capacity std::vector
116- * 3. std::vector reallocates → reading thread accesses deallocated memory
114+ * std::map is ideal for thread-safe concurrent access: insertions never invalidate
115+ * references/pointers to existing elements (guaranteed by C++ standard §26.2.6).
116+ * This eliminates the need for mutex locks during concurrent read-during-write.
117117 *
118- * Using std::deque also improves performance by avoiding reallocation costs.
119- * Memory locality is preserved because we store pointers to RawCSVField[], not
120- * the objects themselves.
118+ * Unlike std::deque, whose internal map can reallocate (invalidating ALL operator[]
119+ * calls even for old elements), std::map provides stable element access.
121120 *
122- * See: Issue #217, PR #237, v2.3.0 (June 2024)
121+ * Performance: O(log n) access where n = blocks per 10MB chunk. Pathological case
122+ * (1-char rows): 10MB / 2 bytes = 5M fields / 170 per block ≈ 29K blocks.
123+ * log₂(29K) ≈ 15 comparisons << mutex contention cost.
124+ *
125+ * See: Issue #217, PR #237, v2.3.0 (June 2024); Sanitizer fixes Feb 2026
123126 */
124- std::deque< std::unique_ptr<RawCSVField[]>> buffers = {};
127+ std::map< size_t , std::unique_ptr<RawCSVField[]>> buffers = {};
125128
126129 /* * Number of items in the current buffer */
127130 size_t _current_buffer_size = 0 ;
128131
132+ /* * Current block number */
133+ size_t _current_block = 0 ;
134+
129135 /* * Pointer to the current empty field */
130136 RawCSVField* _back = nullptr ;
131137
0 commit comments