Skip to content

Commit dc346b1

Browse files
committed
Careful mutating the global worktodo.txt
1 parent 34b3eb5 commit dc346b1

File tree

8 files changed

+131
-69
lines changed

8 files changed

+131
-69
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ STRIP=-s
3131

3232
endif
3333

34-
SRCS1 = TuneEntry.cpp Primes.cpp tune.cpp CycleFile.cpp TrigBufCache.cpp Event.cpp Queue.cpp TimeInfo.cpp Profile.cpp bundle.cpp Saver.cpp KernelCompiler.cpp Kernel.cpp gpuid.cpp File.cpp Proof.cpp log.cpp Worktodo.cpp common.cpp main.cpp Gpu.cpp clwrap.cpp Task.cpp timeutil.cpp Args.cpp state.cpp Signal.cpp FFTConfig.cpp AllocTrac.cpp sha3.cpp md5.cpp version.cpp
34+
SRCS1 = fs.cpp TuneEntry.cpp Primes.cpp tune.cpp CycleFile.cpp TrigBufCache.cpp Event.cpp Queue.cpp TimeInfo.cpp Profile.cpp bundle.cpp Saver.cpp KernelCompiler.cpp Kernel.cpp gpuid.cpp File.cpp Proof.cpp log.cpp Worktodo.cpp common.cpp main.cpp Gpu.cpp clwrap.cpp Task.cpp timeutil.cpp Args.cpp state.cpp Signal.cpp FFTConfig.cpp AllocTrac.cpp sha3.cpp md5.cpp version.cpp
3535

3636
SRCS2 = test.cpp
3737

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_executable(gpuowl
1919
CycleFile.cpp
2020
tune.cpp
2121
TuneEntry.cpp
22+
fs.cpp
2223
)
2324

2425
target_link_libraries(gpuowl OpenCL)

src/CycleFile.cpp

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,19 @@
11
// Copyright (C) Mihai Preda
22

33
#include "CycleFile.h"
4-
#include <filesystem>
4+
#include "fs.h"
55

66
CycleFile::CycleFile(const fs::path& name, bool keepOld) :
77
name{name},
88
f{File::openWrite(name + ".new")},
99
keepOld{keepOld}
1010
{}
1111

12+
1213
CycleFile::~CycleFile() {
1314
if (!f) { return; }
14-
15-
string old = name + ".bak";
16-
string tmp = f->name;
17-
1815
f.reset();
19-
20-
std::error_code dummy;
21-
try {
22-
if (keepOld) { fs::rename(name, old, dummy); }
23-
24-
// The normal behavior of rename() is to unlink a pre-existing destination name and to rename successfully
25-
// See https://en.cppreference.com/w/cpp/filesystem/rename
26-
// But this behavior is not obeyed by some Windows implementations (mingw/msys?)
27-
fs::rename(tmp, name);
28-
} catch (const fs::filesystem_error& e) {
29-
// So if rename() throws, we attempt to remove the destination explicitly
30-
fs::remove(old, dummy);
31-
fs::rename(name, old, dummy);
32-
fs::rename(tmp, name, dummy);
33-
if (!keepOld) { fs::remove(old, dummy); }
34-
}
16+
fancyRename(name + ".new", name, keepOld);
3517
}
3618

3719
File* CycleFile::operator->() { return f.operator->(); }

src/File.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@
2626

2727
namespace fs = std::filesystem;
2828

29-
inline fs::path operator+(fs::path p, const std::string& tail) {
30-
p += tail;
31-
return p;
32-
}
33-
3429
class File {
3530
FILE* f = nullptr;
3631
const bool readOnly;

src/Worktodo.cpp

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
#include "Worktodo.h"
44

5-
#include "CycleFile.h"
65
#include "Task.h"
76
#include "File.h"
87
#include "common.h"
98
#include "Args.h"
9+
#include "fs.h"
1010

1111
#include <cassert>
1212
#include <string>
1313
#include <optional>
14-
#include <mutex>
1514
#include <charconv>
1615

1716
namespace {
@@ -68,28 +67,6 @@ std::optional<Task> parse(const std::string& line) {
6867
return {};
6968
}
7069

71-
bool deleteLine(const fs::path& fileName, const std::string& targetLine) {
72-
assert(!targetLine.empty());
73-
bool lineDeleted = false;
74-
75-
CycleFile fo{fileName};
76-
for (const string& line : File::openReadThrow(fileName)) {
77-
// log("line '%s'\n", line.c_str());
78-
if (!lineDeleted && line == targetLine) {
79-
lineDeleted = true;
80-
} else {
81-
fo->write(line);
82-
}
83-
}
84-
85-
if (!lineDeleted) {
86-
log("'%s': did not find the line '%s' to delete\n", fileName.string().c_str(), targetLine.c_str());
87-
fo.reset();
88-
}
89-
90-
return lineDeleted;
91-
}
92-
9370
// Among the valid tasks from fileName, return the "best" which means with the smallest exponent
9471
static std::optional<Task> bestTask(const fs::path& fileName) {
9572
optional<Task> best;
@@ -100,34 +77,60 @@ static std::optional<Task> bestTask(const fs::path& fileName) {
10077
return best;
10178
}
10279

103-
static string workName(i32 instance) { return "work-" + to_string(instance) + ".txt"; }
80+
string workName(i32 instance) { return "worktodo-" + to_string(instance) + ".txt"; }
10481

10582
optional<Task> getWork(Args& args, i32 instance) {
106-
static mutex mut;
107-
108-
again:
109-
// Try to get a task from the local work-<N> file.
110-
// This only reads from the per-worker file, so we don't need to lock.
83+
// Try to get a task from the local worktodo-<N> file.
11184
if (optional<Task> task = bestTask(workName(instance))) { return task; }
11285

113-
// Try in order: the local worktodo.txt, and the global worktodo.txt if set up.
114-
vector<fs::path> worktodoFiles{"worktodo.txt"};
115-
if (!args.masterDir.empty()) { worktodoFiles.push_back(args.masterDir / "worktodo.txt"); }
86+
if (args.masterDir.empty()) { return {}; }
87+
88+
fs::path worktodo = args.masterDir / "worktodo.txt";
11689

117-
lock_guard lock(mut);
90+
/*
91+
We need to aquire a task from the global worktodo.txt, and "atomically"
92+
add the task to the local worktodo-N.txt and remove it from worktodo.txt
11893
119-
for (fs::path& worktodo : worktodoFiles) {
120-
if (optional<Task> task = bestTask(worktodo)) {
121-
File::append(workName(instance), task->line);
122-
deleteLine(worktodo, task->line);
123-
goto again;
94+
Below we call the global worktodo.txt "global worktodo", and worktodo-N.txt "local worktodo".
95+
96+
We want to avoid filesystem-based locking, so we approximate it this way:
97+
1. read the file-size of the global worktodo
98+
2. read one task from the global worktodo
99+
3. append the task to the local worktodo
100+
4. write the new content of the global worktodo without the task to a temporary file
101+
5. compare the size of the global worktodo with its initial size (as an heuristic to detect modifications to it)
102+
6a. if the size is not changed, rename the temporary file to global worktodo and done
103+
6b. if the size is changed (i.e. global worktodo was modified in the meantime):
104+
7. remove the task from the local worktodo (undo the local task add)
105+
8. start again (from step 1)
106+
*/
107+
108+
for (int retry = 0; retry < 2; ++retry) {
109+
u64 initialSize = fileSize(worktodo);
110+
if (!initialSize) { return {}; }
111+
112+
optional<Task> task = bestTask(worktodo);
113+
if (!task) { return {}; }
114+
115+
string workLine = task->line;
116+
File::append(workName(instance), workLine);
117+
118+
if (deleteLine(worktodo, workLine, initialSize)) {
119+
return task;
124120
}
121+
122+
// Undo add to local worktodo
123+
[[maybe_unused]] bool found = deleteLine(workName(instance), workLine);
124+
assert(found);
125125
}
126126

127-
return std::nullopt;
127+
log("Could not extract a task from '%s'\n", worktodo.string().c_str());
128+
// must be tough luck to be preempted twice while mutating the global worktodo
129+
assert(false);
130+
return {};
128131
}
129132

130-
}
133+
} // namespace
131134

132135
std::optional<Task> Worktodo::getTask(Args &args, i32 instance) {
133136
if (instance == 0) {

src/common.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
#include <iomanip>
1313
#include <filesystem>
1414

15-
1615
string hex(u64 x) {
1716
ostringstream out{};
1817
out << setbase(16) << setfill('0') << setw(16) << x;

src/fs.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (C) Mihai Preda
2+
3+
#include "fs.h"
4+
#include "File.h"
5+
6+
#include <thread>
7+
8+
namespace {
9+
10+
std::string toString(const std::thread::id& x) {
11+
std::ostringstream os;
12+
os << x;
13+
return os.str();
14+
}
15+
16+
bool sizeMatches(const fs::path& path, u64 initialSize) {
17+
error_code dummy;
18+
return !initialSize || (initialSize == fs::file_size(path, dummy));
19+
}
20+
21+
} // namespace
22+
23+
void fancyRename(const fs::path& src, const fs::path& dst, bool keepOld) {
24+
try {
25+
// The normal behavior of rename() is to unlink a pre-existing destination name and to rename successfully
26+
// See https://en.cppreference.com/w/cpp/filesystem/rename
27+
// But this behavior is not obeyed by some Windows implementations (mingw/msys?)
28+
if (keepOld) { fs::rename(dst, dst + ".bak"); }
29+
fs::rename(src, dst);
30+
} catch (const fs::filesystem_error& e) {
31+
// So if rename() throws, we attempt to remove the destination explicitly
32+
std::error_code dummy;
33+
fs::remove(dst + ".bak", dummy);
34+
fs::rename(dst, dst + ".bak", dummy);
35+
fs::rename(src, dst); // If this rename does not succeed, let it throw
36+
if (!keepOld) { fs::remove(dst + ".bak", dummy); }
37+
}
38+
}
39+
40+
u64 fileSize(const fs::path& path) {
41+
error_code dummy;
42+
auto size = fs::file_size(path, dummy);
43+
if (size == decltype(size)(-1)) { size = 0; }
44+
return size;
45+
}
46+
47+
bool deleteLine(const fs::path& path, const string& targetLine, u64 initialSize) {
48+
fs::path tmp = path + ("-"s + toString(this_thread::get_id()));
49+
50+
File fi = File::openRead(path);
51+
File fo = File::openWrite(tmp);
52+
53+
bool found = false;
54+
for (const string& line : fi) {
55+
if (!found && line == targetLine) {
56+
found = true;
57+
} else {
58+
fo.write(line);
59+
}
60+
}
61+
62+
if (!found || !sizeMatches(path, initialSize)) { return false; }
63+
64+
fancyRename(tmp, path);
65+
return true;
66+
}

src/fs.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (C) Mihai Preda
2+
3+
#include "common.h"
4+
5+
#include <filesystem>
6+
7+
inline fs::path operator+(fs::path p, const std::string& tail) {
8+
p += tail;
9+
return p;
10+
}
11+
12+
u64 fileSize(const fs::path& path);
13+
14+
void fancyRename(const fs::path& src, const fs::path& dst, bool keepOld = false);
15+
16+
bool deleteLine(const fs::path& path, const string& targetLine, u64 initialSize = 0);

0 commit comments

Comments
 (0)