-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Chdir: merge redundant fchdir() system calls
This commit adds a class which manages fchdir() system calls and lets multiple callers use the same directory. This prepares for submitting "cull" commands to `/dev/cachefiles` with io_uring.
- Loading branch information
1 parent
f40746f
commit b7ab4d9
Showing
9 changed files
with
297 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// SPDX-License-Identifier: BSD-2-Clause | ||
// Copyright CM4all GmbH | ||
// author: Max Kellermann <[email protected]> | ||
|
||
#include "Chdir.hxx" | ||
|
||
#include <unistd.h> | ||
|
||
Chdir::Chdir(EventLoop &event_loop) noexcept | ||
:defer_next(event_loop, BIND_THIS_METHOD(Next)) | ||
{ | ||
} | ||
|
||
Chdir::~Chdir() noexcept | ||
{ | ||
/* revert to "/" so we don't occupy an arbitrary directory | ||
(that would prevent unmounting, for example) */ | ||
chdir("/"); | ||
} | ||
|
||
inline void | ||
Chdir::Next() noexcept | ||
{ | ||
/* if there is a valid "current" directory, we don't need to | ||
fchdir() again */ | ||
const bool need_chdir = current == map.end(); | ||
|
||
if (current == map.end()) { | ||
/* find the first non-empty list, skipping canceled | ||
directories */ | ||
while (true) { | ||
current = map.begin(); | ||
if (current == map.end()) | ||
/* no waiters left - stop here */ | ||
return; | ||
|
||
if (!current->second.empty()) | ||
/* this is not empty - use it */ | ||
break; | ||
|
||
/* all waiters have been canceled; remove this | ||
directory; the FileDescriptor may have been closed | ||
already, but this doesn't matter, we don't use it | ||
anyway */ | ||
current = map.erase(current); | ||
} | ||
} | ||
|
||
auto &list = current->second; | ||
|
||
if (need_chdir && fchdir(current->first.Get()) < 0) { | ||
// error | ||
|
||
/* move the list to the stack and erase the failed | ||
directory from the map */ | ||
auto tmp = std::move(list); | ||
|
||
map.erase(current); | ||
current = map.end(); | ||
|
||
/* schedule the next fchdir() after the callback loop | ||
below has finished */ | ||
defer_next.Schedule(); | ||
|
||
list.clear_and_dispose([](auto *i){ | ||
i->OnChdirError(); | ||
}); | ||
} else { | ||
/* success: create a lease first; this is kept alive | ||
until all waiters have been invoked to avoid | ||
OnAbandoned() to be called in the middle of this | ||
loop */ | ||
SharedLease lease{*this}; | ||
|
||
while (true) { | ||
auto &i = list.pop_front(); | ||
if (list.empty()) { | ||
/* the last waiter inherits this | ||
lease, so it won't be released at | ||
the end of this scope */ | ||
i.OnChdir(std::move(lease)); | ||
break; | ||
} | ||
|
||
/* all other waiters get a copy of the | ||
lease */ | ||
i.OnChdir(lease); | ||
} | ||
} | ||
} | ||
|
||
void | ||
Chdir::Add(FileDescriptor directory, ChdirWaiter &w) noexcept | ||
{ | ||
map[directory].push_back(w); | ||
|
||
if (current == map.end() || current->first == directory) | ||
defer_next.Schedule(); | ||
} | ||
|
||
void | ||
Chdir::OnAbandoned() noexcept | ||
{ | ||
assert(current != map.end()); | ||
|
||
map.erase(current); | ||
current = map.end(); | ||
|
||
defer_next.Schedule(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// SPDX-License-Identifier: BSD-2-Clause | ||
// Copyright CM4all GmbH | ||
// author: Max Kellermann <[email protected]> | ||
|
||
#pragma once | ||
|
||
#include "event/DeferEvent.hxx" | ||
#include "io/FileDescriptor.hxx" | ||
#include "util/IntrusiveList.hxx" | ||
#include "util/SharedLease.hxx" | ||
|
||
#include <map> | ||
|
||
/** | ||
* Handler class for #Chdir. | ||
*/ | ||
class ChdirWaiter { | ||
IntrusiveListHook<IntrusiveHookMode::AUTO_UNLINK> chdir_siblings; | ||
|
||
public: | ||
using List = IntrusiveList<ChdirWaiter, IntrusiveListMemberHookTraits<&ChdirWaiter::chdir_siblings>>; | ||
|
||
/** | ||
* The current working directory is now the one passed to | ||
* Chdir::Add(). This method may now perform I/O, even after | ||
* this method has returned. When all I/O in this directory | ||
* is complete, release the #lease. | ||
*/ | ||
virtual void OnChdir(SharedLease lease) noexcept = 0; | ||
|
||
/** | ||
* An (unspecified) error has occurred and the directory could | ||
* not be changed. | ||
*/ | ||
virtual void OnChdirError() noexcept = 0; | ||
}; | ||
|
||
/** | ||
* Provides an optimization for changing the current working directory | ||
* using fchdir(): multiple callers can ask to change the working | ||
* directory and this class groups all callers with the same | ||
* directory. It calls them back using the #ChdirWaiter interface, | ||
* waits for all leases to be released, and then goes on with the next | ||
* directory. | ||
*/ | ||
class Chdir final : SharedAnchor { | ||
DeferEvent defer_next; | ||
|
||
struct FileDescriptorCompare { | ||
constexpr bool operator()(FileDescriptor a, FileDescriptor b) const noexcept { | ||
return a.Get() < b.Get(); | ||
} | ||
}; | ||
|
||
using WaiterMap = std::map<FileDescriptor, ChdirWaiter::List, FileDescriptorCompare>; | ||
|
||
/** | ||
* Maps directory file descriptors to a list of waiters. The | ||
* file descriptors are owned by the waiters. | ||
*/ | ||
WaiterMap map; | ||
|
||
/** | ||
* If this is not end(), then this points to the current | ||
* working directory and there are unreleased leases. | ||
*/ | ||
WaiterMap::iterator current = map.end(); | ||
|
||
public: | ||
explicit Chdir(EventLoop &event_loop) noexcept; | ||
~Chdir() noexcept; | ||
|
||
Chdir(const Chdir &) = delete; | ||
Chdir &operator=(const Chdir &) = delete; | ||
|
||
/** | ||
* Schedule a fchdir() call. | ||
* | ||
* @param directory where to change to; must remain valid | ||
* until the #ChdirWaiter has been called | ||
* @param w the waiter that will be called back; it may be | ||
* canceled by simply destructing the #ChdirWaiter instance | ||
*/ | ||
void Add(FileDescriptor directory, ChdirWaiter &w) noexcept; | ||
|
||
private: | ||
void Next() noexcept; | ||
|
||
// virtual methods from SharedAnchor | ||
void OnAbandoned() noexcept override; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,60 @@ | |
// author: Max Kellermann <[email protected]> | ||
|
||
#include "Cull.hxx" | ||
#include "util/DeleteDisposer.hxx" | ||
|
||
#include <cassert> | ||
#include <memory> | ||
|
||
#include <fmt/core.h> // TODO | ||
|
||
class Cull::CullFileOperation final : public IntrusiveListHook<>, ChdirWaiter { | ||
Cull &cull; | ||
|
||
std::unique_ptr<WalkResult::File> file; | ||
|
||
public: | ||
CullFileOperation(Cull &_cull, WalkResult::File *_file) noexcept | ||
:cull(_cull), file(_file) {} | ||
|
||
void Start() noexcept { | ||
cull.chdir.Add(file->parent->fd, *this); | ||
} | ||
|
||
// virtual methods from ChdirWaiter | ||
void OnChdir(SharedLease lease) noexcept override; | ||
void OnChdirError() noexcept override; | ||
}; | ||
|
||
void | ||
Cull::CullFileOperation::OnChdir(SharedLease lease) noexcept | ||
{ | ||
switch (cull.dev_cachefiles.CullFile(file->name)) { | ||
case DevCachefiles::CullResult::SUCCESS: | ||
++cull.n_deleted_files; | ||
cull.n_deleted_bytes += file->size; | ||
break; | ||
|
||
case DevCachefiles::CullResult::BUSY: | ||
++cull.n_busy; | ||
break; | ||
|
||
case DevCachefiles::CullResult::ERROR: | ||
++cull.n_errors; | ||
break; | ||
} | ||
|
||
lease = {}; | ||
cull.OperationFinished(*this); | ||
} | ||
|
||
void | ||
Cull::CullFileOperation::OnChdirError() noexcept | ||
{ | ||
++cull.n_errors; | ||
cull.OperationFinished(*this); | ||
} | ||
|
||
static DevCachefiles::CullResult | ||
CullFile(DevCachefiles &dev_cachefiles, | ||
FileDescriptor directory_fd, std::string_view filename) | ||
|
@@ -19,13 +68,14 @@ CullFile(DevCachefiles &dev_cachefiles, | |
return dev_cachefiles.CullFile(filename); | ||
} | ||
|
||
Cull::Cull(Uring::Queue &_uring, | ||
Cull::Cull(EventLoop &event_loop, Uring::Queue &_uring, | ||
FileDescriptor _dev_cachefiles, | ||
uint_least64_t _cull_files, std::size_t _cull_bytes, | ||
Callback _callback) | ||
:dev_cachefiles(_dev_cachefiles), | ||
callback(_callback), | ||
walk(_uring, _cull_files, _cull_bytes, *this) | ||
walk(_uring, _cull_files, _cull_bytes, *this), | ||
chdir(event_loop) | ||
{ | ||
assert(callback); | ||
} | ||
|
@@ -51,34 +101,41 @@ Cull::OnWalkFinished(WalkResult &&result) noexcept | |
fmt::print(stderr, "Cull: delete {} files, {} bytes\n", | ||
result.files.size(), result.total_bytes); | ||
|
||
std::size_t n_deleted_files = 0, n_busy = 0; | ||
uint_least64_t n_deleted_bytes = 0, n_errors = 0; | ||
|
||
result.files.clear_and_dispose([&](WalkResult::File *file){ | ||
#ifndef NDEBUG | ||
result.total_bytes -= file->size; | ||
#endif | ||
|
||
switch (CullFile(dev_cachefiles, file->parent->fd, file->name.c_str())) { | ||
case DevCachefiles::CullResult::SUCCESS: | ||
++n_deleted_files; | ||
n_deleted_bytes += file->size; | ||
break; | ||
auto *op = new CullFileOperation(*this, file); | ||
new_operations.push_back(*op); | ||
}); | ||
|
||
case DevCachefiles::CullResult::BUSY: | ||
++n_busy; | ||
break; | ||
assert(result.total_bytes == 0); | ||
|
||
case DevCachefiles::CullResult::ERROR: | ||
++n_errors; | ||
} | ||
if (operations.empty() && new_operations.empty()) { | ||
Finish(); | ||
return; | ||
} | ||
|
||
delete file; | ||
new_operations.clear_and_dispose([this](auto *op){ | ||
operations.push_back(*op); | ||
op->Start(); | ||
}); | ||
} | ||
|
||
fmt::print(stderr, "Cull: deleted {} files, {} bytes; {} in use; {} errors\n", n_deleted_files, n_deleted_bytes, n_busy, n_errors); | ||
void | ||
Cull::OperationFinished(CullFileOperation &op) noexcept | ||
{ | ||
assert(!operations.empty()); | ||
|
||
assert(result.total_bytes == 0); | ||
operations.erase_and_dispose(operations.iterator_to(op), DeleteDisposer{}); | ||
if (operations.empty() && new_operations.empty()) | ||
Finish(); | ||
} | ||
|
||
void | ||
Cull::Finish() noexcept | ||
{ | ||
fmt::print(stderr, "Cull: deleted {} files, {} bytes; {} in use; {} errors\n", n_deleted_files, n_deleted_bytes, n_busy, n_errors); | ||
callback(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.