Skip to content

Commit aa237bc

Browse files
Add eraseAt and thread-safe getNextElement functions to CustomVector
Agent-Logs-Url: https://github.com/WEBcodeX1/http-1.2/sessions/87509d2f-514a-4b2a-9684-13976b662d40 Co-authored-by: clauspruefer <17313789+clauspruefer@users.noreply.github.com>
1 parent 30edeec commit aa237bc

3 files changed

Lines changed: 298 additions & 0 deletions

File tree

doc/CustomVector.rst

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Key Features
1414
3. **Contiguous addressing**: Next segment address = previous address + segment_size_bytes
1515
4. **No iterators**: Designed to be 100% compatible with shared memory (no types that rely on other memory regions)
1616
5. **Simple interface**: Provides essential vector operations only
17+
6. **Thread-safe operations**: ``getNextElement()`` is thread-safe for multi-process/multi-threaded access
1718

1819
Required Functions
1920
------------------
@@ -23,6 +24,8 @@ Required Functions
2324
3. ✅ **push_back** element
2425
4. ✅ **get element** at element index
2526
5. ✅ **get elements count**
27+
6. ✅ **eraseAt** (remove element at specific index)
28+
7. ✅ **getNextElement** (thread-safe: get first element and remove it)
2629

2730
Usage Example
2831
-------------
@@ -278,6 +281,59 @@ segment_size()
278281
279282
Returns the segment size in bytes.
280283

284+
eraseAt()
285+
~~~~~~~~~
286+
287+
.. code-block:: cpp
288+
289+
void eraseAt(size_t index)
290+
291+
Removes element at specified index. Throws ``std::out_of_range`` if index is out of bounds.
292+
293+
This operation shifts all elements after the removed element forward by one position. Time complexity is O(n) where n is the number of elements after the removed element.
294+
295+
**Example:**
296+
297+
.. code-block:: cpp
298+
299+
CustomVector<int> vec(sizeof(int), shmem, 4096);
300+
vec.push_back(10);
301+
vec.push_back(20);
302+
vec.push_back(30);
303+
304+
vec.eraseAt(1); // Removes element at index 1 (value 20)
305+
// Vector now contains: [10, 30]
306+
307+
getNextElement()
308+
~~~~~~~~~~~~~~~~
309+
310+
.. code-block:: cpp
311+
312+
T getNextElement()
313+
314+
**Thread-safe** operation that returns the first element and removes it from the vector. Throws ``std::out_of_range`` if vector is empty.
315+
316+
This function is thread-safe and can be called from multiple threads or processes. It uses a process-shared mutex to ensure atomic get-and-remove operation, making it suitable for producer-consumer patterns in shared memory.
317+
318+
**Example:**
319+
320+
.. code-block:: cpp
321+
322+
CustomVector<int> vec(sizeof(int), shmem, 4096);
323+
vec.push_back(100);
324+
vec.push_back(200);
325+
326+
int first = vec.getNextElement(); // Returns 100, removes it
327+
int second = vec.getNextElement(); // Returns 200, removes it
328+
// Vector is now empty
329+
330+
**Thread-Safety:**
331+
332+
The ``getNextElement()`` function uses a pthread mutex with ``PTHREAD_PROCESS_SHARED`` attribute, making it safe for:
333+
334+
- Multi-threaded access within a single process
335+
- Multi-process access when the CustomVector is in shared memory
336+
281337
Testing
282338
-------
283339

src/CustomVector.hpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <cstdlib>
55
#include <stdexcept>
66
#include <new>
7+
#include <pthread.h>
8+
#include <cstring>
79

810
/**
911
* Minimalistic vector implementation with custom memory management.
@@ -17,6 +19,7 @@
1719
* - The data memory uses contiguous segment-based addressing
1820
* - Does not own the memory (no malloc/free)
1921
* - Template parameter T should be trivially copyable for optimal shared memory usage
22+
* - Thread-safe getNextElement() operation using pthread mutex
2023
*
2124
* Usage:
2225
* void* shmem = mmap(NULL, 640000, PROT_READ | PROT_WRITE,
@@ -33,6 +36,7 @@ class CustomVector {
3336
size_t size_; // Current number of elements
3437
char* memory_base_; // Base address of shared memory
3538
size_t memory_total_bytes_; // Total size of shared memory region
39+
pthread_mutex_t mutex_; // Mutex for thread-safe operations
3640

3741
public:
3842
/**
@@ -56,6 +60,13 @@ class CustomVector {
5660
if (shared_memory_size == 0) {
5761
throw std::invalid_argument("Shared memory size must be greater than 0");
5862
}
63+
64+
// Initialize mutex with process-shared attribute for cross-process synchronization
65+
pthread_mutexattr_t attr;
66+
pthread_mutexattr_init(&attr);
67+
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
68+
pthread_mutex_init(&mutex_, &attr);
69+
pthread_mutexattr_destroy(&attr);
5970
}
6071

6172
/**
@@ -67,6 +78,8 @@ class CustomVector {
6778
for (size_t i = 0; i < size_; ++i) {
6879
get_element_ptr(i)->~T();
6980
}
81+
// Destroy the mutex
82+
pthread_mutex_destroy(&mutex_);
7083
// Note: We do NOT free memory_base_ as it's externally managed (e.g., via munmap)
7184
}
7285

@@ -164,6 +177,68 @@ class CustomVector {
164177
return segment_size_bytes_;
165178
}
166179

180+
/**
181+
* Removes element at specified index
182+
* @param index Index of the element to remove
183+
* @throws std::out_of_range if index is out of bounds
184+
*
185+
* This operation shifts all elements after the removed element forward by one position.
186+
* Time complexity: O(n) where n is the number of elements after the removed element.
187+
*/
188+
void eraseAt(size_t index) {
189+
if (index >= size_) {
190+
throw std::out_of_range("Index out of bounds");
191+
}
192+
193+
// Destroy the element at the index
194+
get_element_ptr(index)->~T();
195+
196+
// Shift all elements after the erased element forward
197+
for (size_t i = index; i < size_ - 1; ++i) {
198+
T* current = get_element_ptr(i);
199+
T* next = get_element_ptr(i + 1);
200+
201+
// Use placement new to copy-construct in place
202+
new (current) T(*next);
203+
204+
// Destroy the old element
205+
next->~T();
206+
}
207+
208+
--size_;
209+
}
210+
211+
/**
212+
* Thread-safe operation: returns the first element and removes it from the vector
213+
* @return Copy of the first element
214+
* @throws std::out_of_range if vector is empty
215+
*
216+
* This function is thread-safe and can be called from multiple threads or processes.
217+
* It uses a mutex to ensure atomic get-and-remove operation.
218+
*/
219+
T getNextElement() {
220+
pthread_mutex_lock(&mutex_);
221+
222+
try {
223+
if (size_ == 0) {
224+
pthread_mutex_unlock(&mutex_);
225+
throw std::out_of_range("Cannot get next element: vector is empty");
226+
}
227+
228+
// Get a copy of the first element
229+
T result = *get_element_ptr(0);
230+
231+
// Remove the first element
232+
eraseAt(0);
233+
234+
pthread_mutex_unlock(&mutex_);
235+
return result;
236+
} catch (...) {
237+
pthread_mutex_unlock(&mutex_);
238+
throw;
239+
}
240+
}
241+
167242
private:
168243
/**
169244
* Gets pointer to element at specified index

test/integration/custom-vector/test-CustomVector.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <iostream>
77
#include <sys/mman.h>
88
#include <cstring>
9+
#include <vector>
910

1011
using namespace std;
1112

@@ -345,3 +346,169 @@ BOOST_AUTO_TEST_CASE(test_custom_vector_placement_new_in_shared_memory) {
345346

346347
munmap(shmpointer, 640000);
347348
}
349+
350+
BOOST_AUTO_TEST_CASE(test_custom_vector_eraseAt) {
351+
cout << "Test CustomVector eraseAt function" << endl;
352+
353+
void* shmem = allocate_shared_memory(4096);
354+
CustomVector<int> vec(sizeof(int), shmem, 4096);
355+
356+
// Add some elements
357+
vec.push_back(10);
358+
vec.push_back(20);
359+
vec.push_back(30);
360+
vec.push_back(40);
361+
vec.push_back(50);
362+
363+
BOOST_CHECK_EQUAL(vec.size(), 5);
364+
cout << "Added 5 elements: 10, 20, 30, 40, 50" << endl;
365+
366+
// Test: Erase element at index 2 (value 30)
367+
vec.eraseAt(2);
368+
BOOST_CHECK_EQUAL(vec.size(), 4);
369+
BOOST_CHECK_EQUAL(vec.at(0), 10);
370+
BOOST_CHECK_EQUAL(vec.at(1), 20);
371+
BOOST_CHECK_EQUAL(vec.at(2), 40); // 40 should have shifted forward
372+
BOOST_CHECK_EQUAL(vec.at(3), 50);
373+
cout << "After erasing index 2: [10, 20, 40, 50]" << endl;
374+
375+
// Test: Erase first element
376+
vec.eraseAt(0);
377+
BOOST_CHECK_EQUAL(vec.size(), 3);
378+
BOOST_CHECK_EQUAL(vec.at(0), 20);
379+
BOOST_CHECK_EQUAL(vec.at(1), 40);
380+
BOOST_CHECK_EQUAL(vec.at(2), 50);
381+
cout << "After erasing index 0: [20, 40, 50]" << endl;
382+
383+
// Test: Erase last element
384+
vec.eraseAt(2);
385+
BOOST_CHECK_EQUAL(vec.size(), 2);
386+
BOOST_CHECK_EQUAL(vec.at(0), 20);
387+
BOOST_CHECK_EQUAL(vec.at(1), 40);
388+
cout << "After erasing index 2: [20, 40]" << endl;
389+
390+
// Test: Erase out of bounds should throw
391+
BOOST_CHECK_THROW(vec.eraseAt(5), std::out_of_range);
392+
cout << "Correctly threw exception for out of bounds erase" << endl;
393+
394+
free_shared_memory(shmem, 4096);
395+
}
396+
397+
BOOST_AUTO_TEST_CASE(test_custom_vector_getNextElement) {
398+
cout << "Test CustomVector getNextElement function" << endl;
399+
400+
void* shmem = allocate_shared_memory(4096);
401+
CustomVector<int> vec(sizeof(int), shmem, 4096);
402+
403+
// Add some elements
404+
vec.push_back(100);
405+
vec.push_back(200);
406+
vec.push_back(300);
407+
408+
BOOST_CHECK_EQUAL(vec.size(), 3);
409+
cout << "Added 3 elements: 100, 200, 300" << endl;
410+
411+
// Test: Get first element (should return 100 and remove it)
412+
int first = vec.getNextElement();
413+
BOOST_CHECK_EQUAL(first, 100);
414+
BOOST_CHECK_EQUAL(vec.size(), 2);
415+
BOOST_CHECK_EQUAL(vec.at(0), 200);
416+
BOOST_CHECK_EQUAL(vec.at(1), 300);
417+
cout << "Got next element: 100, remaining: [200, 300]" << endl;
418+
419+
// Test: Get second element (should return 200 and remove it)
420+
int second = vec.getNextElement();
421+
BOOST_CHECK_EQUAL(second, 200);
422+
BOOST_CHECK_EQUAL(vec.size(), 1);
423+
BOOST_CHECK_EQUAL(vec.at(0), 300);
424+
cout << "Got next element: 200, remaining: [300]" << endl;
425+
426+
// Test: Get third element (should return 300 and remove it)
427+
int third = vec.getNextElement();
428+
BOOST_CHECK_EQUAL(third, 300);
429+
BOOST_CHECK_EQUAL(vec.size(), 0);
430+
cout << "Got next element: 300, remaining: []" << endl;
431+
432+
// Test: Get from empty vector should throw
433+
BOOST_CHECK_THROW(vec.getNextElement(), std::out_of_range);
434+
cout << "Correctly threw exception for empty vector" << endl;
435+
436+
free_shared_memory(shmem, 4096);
437+
}
438+
439+
BOOST_AUTO_TEST_CASE(test_custom_vector_getNextElement_with_struct) {
440+
cout << "Test CustomVector getNextElement with struct type" << endl;
441+
442+
struct Payload_t {
443+
char Payload[128];
444+
uint16_t PayloadLength;
445+
};
446+
447+
void* shmem = allocate_shared_memory(4096);
448+
CustomVector<Payload_t> vec(sizeof(Payload_t), shmem, 4096);
449+
450+
// Add some struct elements
451+
Payload_t p1;
452+
strcpy(p1.Payload, "First payload");
453+
p1.PayloadLength = 13;
454+
vec.push_back(p1);
455+
456+
Payload_t p2;
457+
strcpy(p2.Payload, "Second payload");
458+
p2.PayloadLength = 14;
459+
vec.push_back(p2);
460+
461+
BOOST_CHECK_EQUAL(vec.size(), 2);
462+
cout << "Added 2 struct elements" << endl;
463+
464+
// Test: Get first struct element
465+
Payload_t first = vec.getNextElement();
466+
BOOST_CHECK_EQUAL(std::string(first.Payload), "First payload");
467+
BOOST_CHECK_EQUAL(first.PayloadLength, 13);
468+
BOOST_CHECK_EQUAL(vec.size(), 1);
469+
cout << "Got next element: '" << first.Payload << "' (length: " << first.PayloadLength << ")" << endl;
470+
471+
// Test: Get second struct element
472+
Payload_t second = vec.getNextElement();
473+
BOOST_CHECK_EQUAL(std::string(second.Payload), "Second payload");
474+
BOOST_CHECK_EQUAL(second.PayloadLength, 14);
475+
BOOST_CHECK_EQUAL(vec.size(), 0);
476+
cout << "Got next element: '" << second.Payload << "' (length: " << second.PayloadLength << ")" << endl;
477+
478+
free_shared_memory(shmem, 4096);
479+
}
480+
481+
BOOST_AUTO_TEST_CASE(test_custom_vector_thread_safety) {
482+
cout << "Test CustomVector getNextElement thread safety" << endl;
483+
484+
void* shmem = allocate_shared_memory(8192);
485+
CustomVector<int> vec(sizeof(int), shmem, 8192);
486+
487+
// Add many elements
488+
const int NUM_ELEMENTS = 100;
489+
for (int i = 0; i < NUM_ELEMENTS; ++i) {
490+
vec.push_back(i);
491+
}
492+
493+
BOOST_CHECK_EQUAL(vec.size(), NUM_ELEMENTS);
494+
cout << "Added " << NUM_ELEMENTS << " elements" << endl;
495+
496+
// Test: Sequential getNextElement should work correctly
497+
std::vector<int> retrieved;
498+
for (int i = 0; i < NUM_ELEMENTS; ++i) {
499+
int val = vec.getNextElement();
500+
retrieved.push_back(val);
501+
}
502+
503+
BOOST_CHECK_EQUAL(vec.size(), 0);
504+
BOOST_CHECK_EQUAL(retrieved.size(), NUM_ELEMENTS);
505+
506+
// Verify all elements were retrieved in order
507+
for (int i = 0; i < NUM_ELEMENTS; ++i) {
508+
BOOST_CHECK_EQUAL(retrieved[i], i);
509+
}
510+
cout << "Successfully retrieved all " << NUM_ELEMENTS << " elements in order" << endl;
511+
512+
free_shared_memory(shmem, 8192);
513+
}
514+

0 commit comments

Comments
 (0)