18#include <system_error>
49 fd_ = std::exchange(other.fd_, -1);
50 directory_ = std::move(other.directory_);
51 slab_ = std::move(other.slab_);
52 resource_ = other.resource_;
53 arena_ = std::exchange(other.arena_,
nullptr);
54 region_ = std::move(other.region_);
55 other.region_.size = 0;
56 io_raiser_ = other.io_raiser_;
57 bounded_cache_ = other.bounded_cache_;
58 clean_bytes_read_ = std::exchange(other.clean_bytes_read_, 0);
59 bytes_written_since_sync_ = std::exchange(other.bytes_written_since_sync_, 0);
68 if (slab) (void)slab->region(
resource);
72 if (slab_) slab_->reset(resource_);
74 const std::shared_ptr<ScratchSlab>&
scratch_owner() const noexcept {
return slab_; }
91 if (fd_ >= 0)
throw std::logic_error(
"scratch cache policy changed after open");
92 bounded_cache_ =
true;
96 if (fd_ >= 0 && directory_ !=
value)
97 throw std::logic_error(
"scratch directory changed after spill");
101 if (arena_) arena_->release_region(region_);
103 if (fd_ >= 0) ::close(fd_);
105 clean_bytes_read_ = bytes_written_since_sync_ = 0;
108 if (slab_) slab_->reset(resource_);
110 bool opened() const noexcept {
return fd_ >= 0 || arena_ !=
nullptr; }
116 if (slab_)
throw std::logic_error(
"scratch truncate requires a file or arena");
119 arena_io([&] { arena_->truncate(region_, bytes); });
123 do { result = ::ftruncate(fd_,
static_cast<off_t
>(bytes)); }
124 while (result != 0 && errno == EINTR);
125 if (result != 0)
io_error(errno,
"report scratch truncate");
127 void open(
const char* prefix) {
131 arena_io([&] { arena->open_region(region_); });
136 int directory_error = 0;
141 io_error(directory_error, refusal.c_str());
144 if (name.back() !=
'/') name +=
'/';
147 const int opened = ::mkstemp(name.data());
149 if (::unlink(name.c_str()) != 0) {
150 const int error = errno;
154 if (::fcntl(
opened, F_SETFD, FD_CLOEXEC) == -1) {
155 const int error = errno;
160 if (bounded_cache_) {
161#if defined(__linux__)
162 struct statfs backing{};
163 if (::fstatfs(fd_, &backing) != 0) {
164 const int error = errno;
171 io_error(ENODEV,
"scratch requires disk backing");
173 const int error = ::posix_fadvise(fd_, 0, 0, POSIX_FADV_RANDOM);
176 io_error(error,
"scratch random access");
178#elif defined(__APPLE__)
179 if (::fcntl(fd_, F_NOCACHE, 1) == -1) {
180 const int error = errno;
182 io_error(error,
"scratch uncached access");
192 throw std::logic_error(
"bounded scratch requires an arena region");
194 if (!arena_ || region_.max_bytes != max_bytes)
195 throw std::logic_error(
"bounded scratch policy changed after open");
199 if (!arena)
throw std::logic_error(
"bounded scratch requires an attached arena");
201 arena_io([&] { arena->open_region(region_, max_bytes); });
209 void ensure_size(std::uint64_t bytes, std::uint64_t needed_bytes_lower_bound = 0) {
211 slab_->extend(resource_, bytes);
215 if (!
opened())
throw std::logic_error(
"scratch growth before open");
217 arena_io([&] { arena_->ensure_size(region_, bytes, needed_bytes_lower_bound); });
220 struct stat status{};
222 do { result = ::fstat(fd_, &status); }
while (result != 0 && errno == EINTR);
223 if (result != 0)
io_error(errno,
"scratch size");
224 if (status.st_size >=
static_cast<off_t
>(bytes))
return;
225 do { result = ::ftruncate(fd_,
static_cast<off_t
>(bytes)); }
226 while (result != 0 && errno == EINTR);
227 if (result != 0)
io_error(errno,
"scratch resize");
229 static void check_extent(std::uint64_t offset, std::size_t length,
bool arena =
false,
231 const auto maximum =
static_cast<std::uint64_t
>(std::numeric_limits<off_t>::max());
232 if (offset > maximum || length > maximum - offset) {
236 {{
"reason", arena ?
"arena_io" :
"scratch_io"}});
245 check_extent(offset, length, arena_ !=
nullptr, io_raiser_);
248 const auto count = std::min(length, bounded_cache_
250 :
static_cast<std::size_t
>(std::numeric_limits<ssize_t>::max()));
251 if (slab_) slab_->write_at(resource_, offset,
data, count);
252 else if (arena_) arena_io([&] { arena_->write(region_, offset,
data, count); });
255 const auto chunk = std::min(count, bounded_cache_
257 :
static_cast<std::size_t
>(std::numeric_limits<ssize_t>::max()));
258 const ssize_t written = ::pwrite(fd_,
data, chunk,
static_cast<off_t
>(offset));
259 if (written > 0 && bounded_cache_)
260 bytes_written_since_sync_ +=
static_cast<std::size_t
>(written);
263 return static_cast<ssize_t
>(count);
265 ssize_t
read_some(std::uint64_t offset,
void*
data, std::size_t length)
const {
266 check_extent(offset, length, arena_ !=
nullptr, io_raiser_);
269 const auto count = std::min(length, bounded_cache_ ?
kCacheBytes
270 :
static_cast<std::size_t
>(std::numeric_limits<ssize_t>::max()));
271 if (slab_) slab_->read_at(resource_, offset,
data, count);
272 else if (arena_) arena_io([&] { arena_->read(region_, offset,
data, count); });
275 if (bounded_cache_ && clean_bytes_read_ ==
kCacheBytes) discard_clean_cache();
276 const auto chunk = std::min(count, bounded_cache_
278 :
static_cast<std::size_t
>(std::numeric_limits<ssize_t>::max()));
279 const auto received = ::pread(fd_,
data, chunk,
static_cast<off_t
>(offset));
280 if (received > 0 && bounded_cache_) {
281 clean_bytes_read_ +=
static_cast<std::size_t
>(received);
282 if (clean_bytes_read_ ==
kCacheBytes) discard_clean_cache();
286 return static_cast<ssize_t
>(count);
288 void write(std::uint64_t offset,
const void*
data, std::size_t length) {
290 slab_->write_at(resource_, offset,
data, length);
293 check_extent(offset, length, arena_ !=
nullptr, io_raiser_);
295 arena_io([&] { arena_->write(region_, offset,
data, length); });
298 const auto* bytes =
static_cast<const unsigned char*
>(
data);
299 std::size_t written = 0;
300 while (written < length) {
302 const auto chunk = std::min(length - written, bounded_cache_
304 :
static_cast<std::size_t
>(std::numeric_limits<ssize_t>::max()));
305 const ssize_t count = ::pwrite(fd_, bytes + written, chunk,
306 static_cast<off_t
>(offset + written));
307 if (count < 0 && errno == EINTR)
continue;
308 if (count <= 0)
io_error(count < 0 ? errno : EIO,
"scratch write");
309 written +=
static_cast<std::size_t
>(count);
310 if (bounded_cache_) bytes_written_since_sync_ +=
static_cast<std::size_t
>(count);
313 void read(std::uint64_t offset,
void*
data, std::size_t length)
const {
315 slab_->read_at(resource_, offset,
data, length);
318 check_extent(offset, length, arena_ !=
nullptr, io_raiser_);
320 arena_io([&] { arena_->read(region_, offset,
data, length); });
323 auto* bytes =
static_cast<unsigned char*
>(
data);
324 std::size_t received = 0;
325 while (received < length) {
327 if (bounded_cache_ && clean_bytes_read_ ==
kCacheBytes) discard_clean_cache();
328 const auto chunk = std::min(length - received, bounded_cache_
330 :
static_cast<std::size_t
>(std::numeric_limits<ssize_t>::max()));
331 const ssize_t count = ::pread(fd_, bytes + received, chunk,
332 static_cast<off_t
>(offset + received));
333 if (count < 0 && errno == EINTR)
continue;
334 if (count <= 0)
io_error(count < 0 ? errno : EIO,
"scratch read");
335 received +=
static_cast<std::size_t
>(count);
336 if (bounded_cache_) {
337 clean_bytes_read_ +=
static_cast<std::size_t
>(count);
338 if (clean_bytes_read_ ==
kCacheBytes) discard_clean_cache();
344 template<
class Operation>
void arena_io(Operation operation)
const {
346 catch (
const ReportScratchIoError& error) {
347 const auto& failure = error.run_failure();
352 && failure.args && *failure.args ==
"{\"reason\":\"arena_io\"}")
353 throw AuxiliaryScratchIoError(error);
357 [[noreturn]]
void io_error(
int error,
const char* operation)
const {
359 throw AuxiliaryScratchIoError(error, operation);
360 throw ReportScratchIoError(error, operation, {{
"reason",
"scratch_io"}});
373 void discard_clean_cache()
const {
374 if (!bounded_cache_)
return;
375#if defined(__linux__)
376 const int error = ::posix_fadvise(fd_, 0, 0, POSIX_FADV_DONTNEED);
377 if (error != 0)
io_error(error,
"scratch discard cache");
379 clean_bytes_read_ = 0;
381 void prepare_write() {
382 if (!bounded_cache_ || bytes_written_since_sync_ <
kCacheBytes)
return;
383#if defined(__linux__)
385 do { result = ::fdatasync(fd_); }
while (result != 0 && errno == EINTR);
386 if (result != 0)
io_error(errno,
"scratch flush cache window");
390 discard_clean_cache();
391 bytes_written_since_sync_ = 0;
398 std::string directory_;
401 std::shared_ptr<ScratchSlab> slab_;
403 bool bounded_cache_ =
false;
404 ScratchArena* arena_ =
nullptr;
405 ScratchArena::Region region_;
406 mutable std::size_t clean_bytes_read_ = 0;
407 std::size_t bytes_written_since_sync_ = 0;
static ScratchArena * attached() noexcept
ssize_t write_some(std::uint64_t offset, const void *data, std::size_t length)
ScratchFile(IoRaiser raiser=IoRaiser::report_sink)
ssize_t read_some(std::uint64_t offset, void *data, std::size_t length) const
void truncate(std::uint64_t bytes)
void bind_scratch(const std::shared_ptr< ScratchSlab > &slab, ScratchResource resource)
bool arena_backed() const noexcept
static void check_extent(std::uint64_t offset, std::size_t length, bool arena=false, IoRaiser raiser=IoRaiser::report_sink)
void ensure_size(std::uint64_t bytes, std::uint64_t needed_bytes_lower_bound=0)
static constexpr std::size_t kCacheBytes
void open(const char *prefix)
void set_store(ScratchStore store) noexcept
void write(std::uint64_t offset, const void *data, std::size_t length)
ScratchFile & operator=(const ScratchFile &)=delete
ScratchFile(ScratchFile &&other) noexcept
void set_directory(const std::string &value)
ScratchFile & operator=(ScratchFile &&other) noexcept
void open_bounded(const char *, std::uint64_t max_bytes)
bool opened() const noexcept
ScratchResource scratch_resource() const noexcept
const std::shared_ptr< ScratchSlab > & scratch_owner() const noexcept
int descriptor() const noexcept
ScratchFile(const ScratchFile &)=delete
void read(std::uint64_t offset, void *data, std::size_t length) const
std::string resolve_scratch_directory(const std::string &configured, std::string &refusal, int &error, bool &proven_memory, Probe probe)
ReportScratchIoError ScratchIoError
bool scratch_filesystem_is_memory(std::uint64_t type) noexcept
std::string scratch_create_message(const char *operation, const std::string &directory, const std::string &selection_note)