Skip to content

Commit 5f61871

Browse files
committed
extract: write output asynchronously using a background thread
1 parent 60ffa09 commit 5f61871

2 files changed

Lines changed: 116 additions & 17 deletions

File tree

src/extract/extract.cpp

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,33 +29,112 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
2929
#include <string>
3030
#include <utility>
3131

32-
void Extract::open_file(const osmium::io::Header& header, osmium::io::overwrite output_overwrite, osmium::io::fsync sync, OptionClean const* clean) {
32+
void Extract::writer_loop() {
33+
try {
34+
while (true) {
35+
std::unique_lock<std::mutex> lock{m_mutex};
36+
m_cv.wait(lock, [this]{ return m_flush_pending || m_shutdown; });
37+
38+
if (m_shutdown && !m_flush_pending) {
39+
break;
40+
}
41+
42+
// Reset m_flush_pending under the lock so that swap_and_flush()
43+
// can observe the transition to false only after the buffer has
44+
// been moved out and is no longer shared.
45+
m_clean->apply_to(m_flush_buffer);
46+
auto buf = std::move(m_flush_buffer);
47+
m_flush_buffer = osmium::memory::Buffer{buffer_size,
48+
osmium::memory::Buffer::auto_grow::no};
49+
m_flush_pending = false;
50+
lock.unlock();
51+
m_cv.notify_one();
52+
53+
// The osmium Writer call (compression + I/O) runs outside the lock
54+
// so the main thread can keep filling m_fill_buffer concurrently.
55+
(*m_writer)(std::move(buf));
56+
}
57+
} catch (...) {
58+
std::unique_lock<std::mutex> lock{m_mutex};
59+
m_writer_exception = std::current_exception();
60+
m_flush_pending = false;
61+
m_shutdown = true;
62+
lock.unlock();
63+
m_cv.notify_all();
64+
}
65+
}
66+
67+
void Extract::check_writer_exception() {
68+
if (m_writer_exception) {
69+
std::rethrow_exception(m_writer_exception);
70+
}
71+
}
72+
73+
void Extract::swap_and_flush() {
74+
std::unique_lock<std::mutex> lock{m_mutex};
75+
m_cv.wait(lock, [this]{ return !m_flush_pending || m_shutdown; });
76+
check_writer_exception();
77+
78+
std::swap(m_fill_buffer, m_flush_buffer);
79+
m_fill_buffer = osmium::memory::Buffer{buffer_size,
80+
osmium::memory::Buffer::auto_grow::no};
81+
m_flush_pending = true;
82+
lock.unlock();
83+
m_cv.notify_one();
84+
}
85+
86+
void Extract::open_file(const osmium::io::Header& header,
87+
osmium::io::overwrite output_overwrite,
88+
osmium::io::fsync sync,
89+
OptionClean const* clean) {
3390
m_clean = clean;
34-
m_writer = std::make_unique<osmium::io::Writer>(m_output_file, header, output_overwrite, sync);
91+
m_writer = std::make_unique<osmium::io::Writer>(m_output_file, header,
92+
output_overwrite, sync);
93+
m_writer_thread = std::thread{&Extract::writer_loop, this};
3594
}
3695

3796
void Extract::close_file() {
38-
if (m_writer) {
39-
if (m_buffer.committed() > 0) {
40-
m_clean->apply_to(m_buffer);
41-
(*m_writer)(std::move(m_buffer));
97+
if (!m_writer) {
98+
return;
99+
}
100+
101+
if (m_fill_buffer.committed() > 0) {
102+
swap_and_flush();
103+
}
104+
105+
{
106+
std::unique_lock<std::mutex> lock{m_mutex};
107+
m_cv.wait(lock, [this]{ return !m_flush_pending || m_shutdown; });
108+
check_writer_exception();
109+
m_shutdown = true;
110+
}
111+
m_cv.notify_one();
112+
m_writer_thread.join();
113+
114+
check_writer_exception();
115+
m_writer->close();
116+
}
117+
118+
Extract::~Extract() {
119+
if (m_writer_thread.joinable()) {
120+
{
121+
std::lock_guard<std::mutex> lock{m_mutex};
122+
m_shutdown = true;
42123
}
43-
m_writer->close();
124+
m_cv.notify_one();
125+
m_writer_thread.join();
44126
}
45127
}
46128

47129
void Extract::write(const osmium::memory::Item& item) {
48-
if (m_buffer.capacity() - m_buffer.committed() < item.padded_size()) {
49-
m_clean->apply_to(m_buffer);
50-
(*m_writer)(std::move(m_buffer));
51-
m_buffer = osmium::memory::Buffer{buffer_size, osmium::memory::Buffer::auto_grow::no};
130+
if (m_fill_buffer.capacity() - m_fill_buffer.committed() < item.padded_size()) {
131+
swap_and_flush();
52132
}
53-
m_buffer.push_back(item);
133+
m_fill_buffer.push_back(item);
54134
}
55135

56136
std::string Extract::envelope_as_text() const {
57137
std::stringstream ss;
58138
ss << m_envelope;
59139
return ss.str();
60140
}
61-

src/extract/extract.hpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
2929
#include <osmium/io/header.hpp>
3030
#include <osmium/io/writer.hpp>
3131
#include <osmium/io/writer_options.hpp>
32+
#include <osmium/memory/buffer.hpp>
3233
#include <osmium/memory/item.hpp>
3334
#include <osmium/osm/box.hpp>
3435
#include <osmium/osm/location.hpp>
3536

37+
#include <condition_variable>
38+
#include <exception>
3639
#include <memory>
40+
#include <mutex>
3741
#include <string>
42+
#include <thread>
3843
#include <utility>
3944
#include <vector>
4045

@@ -46,20 +51,35 @@ class Extract {
4651
std::string m_description;
4752
std::vector<std::string> m_header_options;
4853
osmium::Box m_envelope;
49-
osmium::memory::Buffer m_buffer{buffer_size, osmium::memory::Buffer::auto_grow::no};
54+
55+
// Double-buffering: the main thread fills m_fill_buffer while the writer
56+
// thread flushes m_flush_buffer to disk asynchronously.
57+
osmium::memory::Buffer m_fill_buffer{buffer_size, osmium::memory::Buffer::auto_grow::no};
58+
osmium::memory::Buffer m_flush_buffer{buffer_size, osmium::memory::Buffer::auto_grow::no};
59+
5060
std::unique_ptr<osmium::io::Writer> m_writer;
5161
const OptionClean* m_clean = nullptr;
5262

63+
std::thread m_writer_thread;
64+
std::mutex m_mutex;
65+
std::condition_variable m_cv;
66+
bool m_flush_pending = false;
67+
bool m_shutdown = false;
68+
std::exception_ptr m_writer_exception;
69+
70+
void writer_loop();
71+
void swap_and_flush();
72+
void check_writer_exception();
73+
5374
public:
5475

5576
Extract(const osmium::io::File& output_file, const std::string& description, const osmium::Box& envelope) :
5677
m_output_file(output_file),
5778
m_description(description),
58-
m_envelope(envelope),
59-
m_writer(nullptr) {
79+
m_envelope(envelope) {
6080
}
6181

62-
virtual ~Extract() = default;
82+
virtual ~Extract();
6383

6484
const std::string& output() const noexcept {
6585
return m_output_file.filename();

0 commit comments

Comments
 (0)