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
1716namespace {
@@ -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
9471static 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
10582optional<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
132135std::optional<Task> Worktodo::getTask (Args &args, i32 instance) {
133136 if (instance == 0 ) {
0 commit comments