Skip to content

Commit 4ff6b44

Browse files
authored
add: Single Writer Multiple Reader (SWMR) support (#68)
1 parent e816184 commit 4ff6b44

File tree

7 files changed

+206
-0
lines changed

7 files changed

+206
-0
lines changed

.github/run_examples.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ fi
1515

1616
for f in "${examples_dir}"/*_bin
1717
do
18+
if [[ "${f}" == *"swmr_"* ]]
19+
then
20+
continue
21+
fi
22+
1823
echo "-- ${f}"
1924
if [[ "${f}" == *"parallel_"* ]]
2025
then
@@ -23,3 +28,12 @@ do
2328
"${f}"
2429
fi
2530
done
31+
32+
for f in "${examples_dir}"/swmr_*_bin
33+
do
34+
[ -f "${f}" ] || continue
35+
echo "-- ${f}"
36+
"${f}" &
37+
done
38+
39+
wait

include/highfive/H5File.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class File: public Object, public NodeTraits<File>, public AnnotateTraits<File>
4040
Debug = 0x10u,
4141
/// Open flag: Create non existing file
4242
Create = 0x20u,
43+
/// Open flag: Open in SWMR read
44+
ReadSWMR = 0x40u,
45+
/// Open flag: Open in SWMR write
46+
WriteSWMR = 0x80u,
4347
/// Derived open flag: common write mode (=ReadWrite|Create|Truncate)
4448
Overwrite = Truncate,
4549
/// Derived open flag: Opens RW or exclusively creates
@@ -54,6 +58,8 @@ class File: public Object, public NodeTraits<File>, public AnnotateTraits<File>
5458
constexpr static AccessMode Create = AccessMode::Create;
5559
constexpr static AccessMode Overwrite = AccessMode::Overwrite;
5660
constexpr static AccessMode OpenOrCreate = AccessMode::OpenOrCreate;
61+
constexpr static AccessMode ReadSWMR = AccessMode::ReadSWMR;
62+
constexpr static AccessMode WriteSWMR = AccessMode::WriteSWMR;
5763

5864
///
5965
/// \brief File
@@ -122,6 +128,11 @@ class File: public Object, public NodeTraits<File>, public AnnotateTraits<File>
122128
///
123129
void flush();
124130

131+
#if H5_VERSION_GE(1, 10, 0)
132+
/// \brief Switches file to SWMR write mode
133+
void startSWMRWrite();
134+
#endif
135+
125136
/// \brief Get the list of properties for creation of this file
126137
FileCreateProps getCreatePropertyList() const {
127138
return details::get_plist<FileCreateProps>(*this, H5Fget_create_plist);

include/highfive/bits/H5File_misc.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ inline unsigned convert_open_flag(File::AccessMode openFlags) {
3434
res_open |= H5F_ACC_TRUNC;
3535
if (any(openFlags & File::Excl))
3636
res_open |= H5F_ACC_EXCL;
37+
#if H5_VERSION_GE(1, 10, 0)
38+
if (any(openFlags & File::ReadSWMR))
39+
res_open |= H5F_ACC_SWMR_READ | H5F_ACC_RDONLY;
40+
if (any(openFlags & File::WriteSWMR))
41+
res_open |= H5F_ACC_SWMR_WRITE | H5F_ACC_RDWR;
42+
#else
43+
if (any(openFlags & (File::ReadSWMR | File::WriteSWMR)))
44+
throw FileException("Your HDF5 library is too old for SWMR mode, you need at least 1.10");
45+
#endif
3746
return res_open;
3847
}
3948
} // namespace
@@ -52,6 +61,9 @@ inline File::File(const std::string& filename,
5261

5362
unsigned createMode = openFlags & (H5F_ACC_TRUNC | H5F_ACC_EXCL);
5463
unsigned openMode = openFlags & (H5F_ACC_RDWR | H5F_ACC_RDONLY);
64+
#if H5_VERSION_GE(1, 10, 0)
65+
openMode |= openFlags & (H5F_ACC_SWMR_READ | H5F_ACC_SWMR_WRITE);
66+
#endif
5567
bool mustCreate = createMode > 0;
5668
bool openOrCreate = (openFlags & H5F_ACC_CREAT) > 0;
5769

@@ -127,6 +139,12 @@ inline void File::flush() {
127139
detail::h5f_flush(_hid, H5F_SCOPE_GLOBAL);
128140
}
129141

142+
#if H5_VERSION_GE(1, 10, 0)
143+
inline void File::startSWMRWrite() {
144+
detail::h5f_start_swmr_write(_hid);
145+
}
146+
#endif
147+
130148
inline size_t File::getFileSize() const {
131149
hsize_t sizeValue = 0;
132150
detail::h5f_get_filesize(_hid, &sizeValue);

include/highfive/bits/h5f_wrapper.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,16 @@ inline hssize_t h5f_get_freespace(hid_t file_id) {
5454
return free_space;
5555
}
5656

57+
#if H5_VERSION_GE(1, 10, 0)
58+
inline herr_t h5f_start_swmr_write(hid_t file_id) {
59+
herr_t err = H5Fstart_swmr_write(file_id);
60+
if (err < 0) {
61+
HDF5ErrMapper::ToException<FileException>(std::string("Failed to start SWMR write"));
62+
}
63+
64+
return err;
65+
}
66+
#endif
67+
5768
} // namespace detail
5869
} // namespace HighFive

src/examples/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ set(half_float_examples
5454
${CMAKE_CURRENT_SOURCE_DIR}/create_dataset_half_float.cpp
5555
)
5656

57+
set(swmr_examples
58+
${CMAKE_CURRENT_SOURCE_DIR}/swmr_read.cpp
59+
${CMAKE_CURRENT_SOURCE_DIR}/swmr_write.cpp
60+
)
61+
5762
function(compile_example example_source)
5863
get_filename_component(example_filename ${example_source} NAME)
5964
string(REPLACE ".cpp" "_bin" example_name ${example_filename})
@@ -98,6 +103,12 @@ if(HDF5_IS_PARALLEL)
98103
endforeach()
99104
endif()
100105

106+
if(HDF5_VERSION VERSION_GREATER_EQUAL 1.10.2)
107+
foreach(example_source ${swmr_examples})
108+
compile_example(${example_source})
109+
endforeach()
110+
endif()
111+
101112
add_library(HighFiveHlHdf5Dependency INTERFACE)
102113
find_package(HDF5 QUIET COMPONENTS HL NAMES HDF5_HL)
103114
if(${HDF5_HL_FOUND})

src/examples/swmr_read.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright (c), 2025, George Sedov
3+
*
4+
* Distributed under the Boost Software License, Version 1.0.
5+
* (See accompanying file LICENSE_1_0.txt or copy at
6+
* http://www.boost.org/LICENSE_1_0.txt)
7+
*
8+
*/
9+
#include <iostream>
10+
#include <string>
11+
#include <thread>
12+
#include <vector>
13+
14+
#include <highfive/highfive.hpp>
15+
16+
const std::string file_name("swmr_read_write.h5");
17+
const std::string dataset_name("array");
18+
19+
/**
20+
* This is the SWMR reader.
21+
* It should be used in conjunction with SWMR writer (see swmr_write example)
22+
*/
23+
int main(void) {
24+
using namespace HighFive;
25+
26+
// give time for the writer to create the file
27+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
28+
29+
// Open file for SWMR read
30+
File file(file_name, File::ReadSWMR);
31+
32+
std::cout << "Started the SWMR read" << std::endl;
33+
34+
auto dataset = file.getDataSet(dataset_name);
35+
auto dims = dataset.getDimensions();
36+
auto old_dims = std::vector<size_t>{0ul};
37+
size_t max_dim = 10;
38+
39+
size_t fail_count = 0;
40+
while (true) {
41+
// refresh is needed for SWMR read
42+
dataset.refresh();
43+
44+
dims = dataset.getDimensions();
45+
// if dimensions changed, it means new data was written to a file
46+
if (dims[0] != old_dims[0]) {
47+
std::vector<size_t> slice{dims[0] - old_dims[0]};
48+
auto values = dataset.select(old_dims, slice).read<std::vector<int>>();
49+
for (const auto& v: values) {
50+
std::cout << v << " ";
51+
}
52+
std::cout << std::flush;
53+
old_dims = dims;
54+
fail_count = 0;
55+
} else {
56+
fail_count++;
57+
}
58+
59+
// there is no way to know that the writer has stopped
60+
// we know that our example writer writes exactly 10 values
61+
if (dims[0] >= max_dim) {
62+
break;
63+
}
64+
65+
// our example writer should add a value every 100 ms
66+
// longer delay means something went wrong
67+
if (fail_count >= 10) {
68+
throw std::runtime_error("SWMR reader timed out.");
69+
}
70+
71+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
72+
}
73+
74+
std::cout << std::endl;
75+
return 0;
76+
}

src/examples/swmr_write.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c), 2025, George Sedov
3+
*
4+
* Distributed under the Boost Software License, Version 1.0.
5+
* (See accompanying file LICENSE_1_0.txt or copy at
6+
* http://www.boost.org/LICENSE_1_0.txt)
7+
*
8+
*/
9+
#include <iostream>
10+
#include <string>
11+
#include <thread>
12+
#include <vector>
13+
14+
#include <highfive/highfive.hpp>
15+
16+
const std::string file_name("swmr_read_write.h5");
17+
const std::string dataset_name("array");
18+
19+
/**
20+
* This is the SWMR writer.
21+
* It should be used in conjunction with SWMR reader (see swmr_read example)
22+
*/
23+
int main(void) {
24+
using namespace HighFive;
25+
26+
// Create a new file
27+
// For SWMR we need to force the latest header, which is passed in AccessProps
28+
FileAccessProps fapl;
29+
fapl.add(FileVersionBounds(H5F_LIBVER_LATEST, H5F_LIBVER_LATEST));
30+
File file(file_name, File::Truncate, fapl);
31+
32+
// To make sense for SWMR, the dataset should be extendable, and hence - chunkable
33+
DataSetCreateProps props;
34+
props.add(Chunking({1}));
35+
auto dataset =
36+
file.createDataSet<int>(dataset_name, DataSpace({0ul}, {DataSpace::UNLIMITED}), props);
37+
38+
// Start the SWMR write
39+
// you are not allowed to create new data headers (i.e. DataSets, Groups, and Attributes) after
40+
// that, you should also make sure all the Groups and Attributes are closed (i.e. the objects
41+
// representing them are out of scope or destroyed) before calling this function
42+
// see HDF5 SWMR tutorial for details
43+
file.startSWMRWrite();
44+
45+
// If you want to open an already-existing file for SWMR write, use
46+
// File file(file_name, File::WriteSWMR);
47+
// auto dataset = file.getDataSet(dataset_name);
48+
49+
std::cout << "Started the SWMR write" << std::endl;
50+
51+
// Let's write to file.
52+
for (int i = 0; i < 10; i++) {
53+
// resize the dataset to fit the new element
54+
dataset.resize({static_cast<size_t>(i + 1)});
55+
// select the dataset slice and write the number to it
56+
dataset.select({static_cast<size_t>(i)}, {1ul}).write(i);
57+
// in SWMR mode you need to explicitly flush the DataSet
58+
dataset.flush();
59+
60+
// give time for the reader to react
61+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
62+
}
63+
64+
return 0;
65+
}

0 commit comments

Comments
 (0)