• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MikkelSchubert / adapterremoval / #117

25 May 2025 03:01PM UTC coverage: 66.932% (-0.07%) from 67.006%
#117

push

travis-ci

web-flow
iwyu and reduce build-time inter-dependencies (#144)

26 of 145 new or added lines in 20 files covered. (17.93%)

89 existing lines in 5 files now uncovered.

9738 of 14549 relevant lines covered (66.93%)

3041.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.77
/src/managed_io.cpp
1
// SPDX-License-Identifier: GPL-3.0-or-later
2
// SPDX-FileCopyrightText: 2015 Mikkel Schubert <mikkelsch@gmail.com>
3
#include "managed_io.hpp"  // declarations
4
#include "buffer.hpp"      // for buffer
5
#include "commontypes.hpp" // for DEV_STDOUT, DEV_STDERR, ...
6
#include "debug.hpp"       // for AR_REQUIRE
7
#include "errors.hpp"      // for io_error
8
#include "logging.hpp"     // for log::warn
9
#include "strutils.hpp"    // for log_escape
10
#include <algorithm>       // for any_of
11
#include <cerrno>          // for EMFILE, errno
12
#include <cstddef>         // for size_t
13
#include <cstdio>          // for fopen, fread, fwrite, ...
14
#include <exception>       // for std::exception
15
#include <fcntl.h>         // for posix_fadvise
16
#include <mutex>           // for mutex, lock_guard
17
#include <string>          // for string
18
#include <string_view>     // for operator==, operator!=
19
#include <sys/stat.h>      // for fstat
20
#include <utility>         // for move
21
#include <vector>          // for vector
22

23
namespace adapterremoval {
24

25
class io_manager
26
{
27
public:
28
  static void open(managed_reader* reader)
1✔
29
  {
30
    if (reader->m_file) {
1✔
31
      return;
32
    } else if (reader->filename() == DEV_STDIN) {
2✔
33
      reader->m_file = stdin;
×
34
    } else if (reader->filename() != DEV_PIPE) {
3✔
35
      reader->m_file = io_manager::fopen(reader->filename(), "rb");
2✔
36

37
#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) ||                        \
38
  (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L)
39
      posix_fadvise(fileno(reader->m_file), 0, 0, POSIX_FADV_WILLNEED);
×
40
      posix_fadvise(fileno(reader->m_file), 0, 0, POSIX_FADV_SEQUENTIAL);
×
41
#endif
42
    } else {
43
      // Merged I/O depends on filenames being identical
44
      AR_FAIL("unhandled STDIN marker");
×
45
    }
46
  }
47

48
  static void open(managed_writer* writer)
49
  {
50
    if (writer->m_file) {
×
51
      return;
52
    } else if (writer->filename() == DEV_STDOUT) {
×
53
      writer->m_file = stdout;
×
54
    } else if (writer->filename() == DEV_STDERR) {
×
55
      // Not sure why anyone would do this, but ¯\_(ツ)_/¯
56
      writer->m_file = stderr;
×
57
    } else if (writer->filename() != DEV_PIPE) {
×
58
      writer->m_file =
×
59
        io_manager::fopen(writer->filename(), writer->m_created ? "ab" : "wb");
×
60
    } else {
61
      // Merged I/O depends on filenames being identical
62
      AR_FAIL("unhandled STDOUT marker");
×
63
    }
64

65
    writer->m_created = true;
×
66
    writer->m_stream =
×
67
      io_manager::is_stream(writer->filename(), writer->m_file);
×
68
  }
69

70
  /** Adds writer to list of inactive writers */
71
  static void add(managed_writer* writer)
72
  {
73
    std::lock_guard<std::mutex> lock(m_lock);
×
74

75
    AR_REQUIRE(!writer->m_prev);
×
76
    AR_REQUIRE(!writer->m_next);
×
77
    AR_REQUIRE(!m_head == !m_tail);
×
78
    if (m_head) {
×
79
      writer->m_next = m_head;
×
80
      m_head->m_prev = writer;
×
81
    }
82

83
    m_head = writer;
×
84

85
    if (!m_tail) {
×
86
      m_tail = writer;
×
87
    }
88

89
    AR_REQUIRE(m_head && m_tail);
×
90
    AR_REQUIRE(!m_head->m_prev);
×
91
    AR_REQUIRE(!m_tail->m_next);
×
92
  }
93

94
  /* Removes the writer from the list of inactive writers */
95
  static void remove(managed_writer* writer)
96
  {
97
    std::lock_guard<std::mutex> lock(m_lock);
×
98

99
    AR_REQUIRE(!m_head == !m_tail);
×
100
    AR_REQUIRE(!m_head || !m_head->m_prev);
×
101
    AR_REQUIRE(!m_tail || !m_tail->m_next);
×
102

103
    if (writer == m_head) {
×
104
      m_head = writer->m_next;
×
105
    }
106

107
    if (writer == m_tail) {
×
108
      m_tail = writer->m_prev;
×
109
    }
110

111
    AR_REQUIRE(!m_head == !m_tail);
×
112

113
    if (writer->m_prev) {
×
114
      writer->m_prev->m_next = writer->m_next;
×
115
    }
116

117
    if (writer->m_next) {
×
118
      writer->m_next->m_prev = writer->m_prev;
×
119
    }
120

121
    writer->m_prev = nullptr;
×
122
    writer->m_next = nullptr;
×
123

124
    AR_REQUIRE(writer != m_head);
×
125
    AR_REQUIRE(writer != m_tail);
×
126
    AR_REQUIRE(!writer->m_prev);
×
127
    AR_REQUIRE(!writer->m_next);
×
128
    AR_REQUIRE(!m_head || !m_head->m_prev);
×
129
    AR_REQUIRE(!m_tail || !m_tail->m_next);
×
130
  }
131

132
  io_manager() = delete;
133
  ~io_manager() = delete;
134
  io_manager(const io_manager&) = delete;
135
  io_manager(io_manager&&) = delete;
136
  io_manager& operator=(const io_manager&) = delete;
137
  io_manager& operator=(io_manager&&) = delete;
138

139
private:
140
  static FILE* fopen(const std::string& filename, const char* mode)
1✔
141
  {
142
    while (true) {
1✔
143
      FILE* handle = ::fopen(filename.c_str(), mode);
2✔
144

145
      if (handle) {
1✔
146
        AR_REQUIRE(!::ferror(handle));
×
147
        return handle;
×
148
      } else if (errno == EMFILE) {
1✔
149
        close_one();
×
150
      } else {
151
        throw io_error("failed to open file", errno);
2✔
152
      }
153
    }
154
  }
155

156
  static bool is_stream(const std::string& filename, FILE* handle)
157
  {
158
    if (handle == stdin || handle == stdout || handle == stderr) {
×
159
      return true;
160
    }
161

162
    struct stat statbuf = {};
×
163
    if (fstat(fileno(handle), &statbuf) == 0) {
×
164
      return S_ISFIFO(statbuf.st_mode);
×
165
    }
166

167
    log::warn() << "Could not fstat " << log_escape(filename);
×
168

169
    // Assumed to be a stream to be safe
170
    return true;
×
171
  }
172

173
  /** Try to close the least recently used writer */
174
  static void close_one()
175
  {
176
    AR_REQUIRE(!m_head == !m_tail);
×
177
    if (!m_warning_printed) {
×
178
      log::warn() << "Number of available file-handles (ulimit -n) is too low. "
×
179
                  << "AdapterRemoval will dynamically close/re-open files as "
×
180
                  << "required, but performance may suffer as a result.";
×
181

182
      m_warning_printed = true;
×
183
    }
184

185
    if (m_tail) {
×
186
      if (fclose(m_tail->m_file)) {
×
187
        m_tail->m_file = nullptr;
×
188
        throw io_error("failed to close file", errno);
×
189
      }
190
      m_tail->m_file = nullptr;
×
191

192
      remove(m_tail);
×
193
    } else {
194
      throw io_error(
×
195
        "available number of file-handles too low; could not open any files");
×
196
    }
197
  }
198

199
  //! Indicates if a performance warning has been printed
200
  static bool m_warning_printed;
201
  //! Most recently used managed_writer
202
  static managed_writer* m_head;
203
  //! Least recently used managed_writer
204
  static managed_writer* m_tail;
205
  //! Lock used to control access to internal state
206
  static std::mutex m_lock;
207
};
208

209
bool io_manager::m_warning_printed = false;
210
managed_writer* io_manager::m_head = nullptr;
211
managed_writer* io_manager::m_tail = nullptr;
212
std::mutex io_manager::m_lock{};
213

214
///////////////////////////////////////////////////////////////////////////////
215

216
managed_reader::managed_reader(FILE* handle)
14✔
217
  : m_filename("<unknown file>")
29✔
218
  , m_file(handle)
14✔
219
{
220
  AR_REQUIRE(handle);
17✔
221
}
14✔
222

223
managed_reader::managed_reader(std::string filename)
1✔
224
  : m_filename(std::move(filename))
2✔
225
{
226
  io_manager::open(this);
1✔
227
}
1✔
228

229
managed_reader::~managed_reader()
13✔
230
{
231
  if (m_file && ::fclose(m_file) != 0) {
13✔
232
    AR_FAIL(format_io_error("error closing " + log_escape(m_filename), errno));
×
233
  }
234
}
13✔
235

236
void
237
managed_reader::close()
×
238
{
239
  if (m_file) {
×
240
    if (::fclose(m_file) != 0) {
×
241
      m_file = nullptr;
×
242
      throw io_error("error closing " + log_escape(m_filename), errno);
×
243
    }
244

245
    m_file = nullptr;
×
246
  }
247
}
248

249
size_t
250
managed_reader::read(void* buffer, size_t size)
33✔
251
{
252
  AR_REQUIRE(buffer);
33✔
253
  const auto nread = ::fread(buffer, 1, size, m_file);
33✔
254
  if (ferror(m_file)) {
33✔
255
    throw io_error("error reading " + log_escape(m_filename), errno);
×
256
  }
257

258
  return nread;
33✔
259
}
260

261
///////////////////////////////////////////////////////////////////////////////
262

263
/** Locker  */
264
class writer_lock
265
{
266
public:
267
  explicit writer_lock(managed_writer* writer)
×
268
    : m_writer(writer)
×
269
  {
270
    AR_REQUIRE(m_writer);
×
271

272
    // Remove from global queue to prevent other threads from manipulating it
273
    io_manager::remove(m_writer);
×
274
    io_manager::open(m_writer);
×
275
  };
276

277
  ~writer_lock()
×
278
  {
279
    // Streams cannot be managed, since they cannot be reopened
280
    if (m_writer->m_file && !m_writer->m_stream) {
×
281
      // Allow this writer to be closed if we run out of file handles
282
      io_manager::add(m_writer);
×
283
    }
284
  }
285

286
  void write(const void* buffer, size_t size)
×
287
  {
288
    AR_REQUIRE(buffer || size == 0);
×
289
    AR_REQUIRE(m_writer && m_writer->m_file);
×
290
    if (size) {
×
291
      const auto ret = ::fwrite(buffer, 1, size, m_writer->m_file);
×
292
      if (ret != size) {
×
293
        throw io_error("error writing to " + log_escape(m_writer->m_filename),
×
294
                       errno);
×
295
      }
296
    }
297
  }
298

299
  void flush()
×
300
  {
301
    AR_REQUIRE(m_writer && m_writer->m_file);
×
302
    if (::fflush(m_writer->m_file)) {
×
303
      throw io_error("error flushing file " + log_escape(m_writer->filename()),
×
304
                     errno);
×
305
    }
306
  }
307

308
  writer_lock(const writer_lock&) = delete;
309
  writer_lock(writer_lock&&) = delete;
310
  writer_lock& operator=(const writer_lock&) = delete;
311
  writer_lock& operator=(writer_lock&&) = delete;
312

313
private:
314
  managed_writer* m_writer;
315
};
316

317
///////////////////////////////////////////////////////////////////////////////
318

319
namespace {
320

321
bool
NEW
322
any_nonempty_buffers(const std::vector<buffer>& buffers)
×
323
{
324
  return std::any_of(buffers.begin(), buffers.end(), [](const auto& it) {
×
325
    return it.size() != 0;
×
326
  });
×
327
}
328

329
} // namespace
330

331
managed_writer::managed_writer(std::string filename)
×
332
  : m_filename(std::move(filename))
×
333
{
334
}
335

336
managed_writer::~managed_writer()
×
337
{
338
  try {
×
339
    close();
×
340
  } catch (const std::exception&) {
×
341
    AR_FAIL("unhandled exception");
×
342
  }
343
}
344

345
void
346
managed_writer::write(const buffer& buf, const flush mode)
×
347
{
348
  if (buf.size() || mode == flush::on) {
×
349
    writer_lock writer{ this };
×
350

351
    writer.write(buf.data(), buf.size());
×
352

353
    if (mode == flush::on) {
×
354
      writer.flush();
×
355
    }
356
  }
357
}
358

359
void
NEW
360
managed_writer::write(const std::vector<buffer>& buffers, const flush mode)
×
361
{
362
  if (mode == flush::on || any_nonempty_buffers(buffers)) {
×
363
    writer_lock writer{ this };
×
364

365
    for (const auto& buf : buffers) {
×
366
      writer.write(buf.data(), buf.size());
×
367
    }
368

369
    if (mode == flush::on) {
×
370
      writer.flush();
×
371
    }
372
  }
373
}
374

375
void
376
managed_writer::write(const std::string& buf, const flush mode)
×
377
{
378
  if (!buf.empty() || mode == flush::on) {
×
379
    writer_lock writer{ this };
×
380

381
    writer.write(buf.data(), buf.length());
×
382

383
    if (mode == flush::on) {
×
384
      writer.flush();
×
385
    }
386
  }
387
}
388

389
void
390
managed_writer::close()
×
391
{
392
  io_manager::remove(this);
×
393
  if (m_file) {
×
394
    if (fclose(m_file)) {
×
395
      m_file = nullptr;
×
396
      throw io_error("failed to close file", errno);
×
397
    }
398

399
    m_file = nullptr;
×
400
  }
401
}
402

403
} // namespace adapterremoval
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc