@@ -40,18 +40,38 @@ std::string pack(std::string_view tag, const Fields& fields) {
4040// Atomic visibility across process loss; storage durability is not promised.
4141bool replace_record (const fs::path& path,std::string_view bytes,std::string* diagnostic=nullptr );
4242bool publish (const fs::path& path,std::string_view tag,const Fields& fields);
43- Record read (const fs::path& path, std::string_view tag) {
44- std::ifstream input (path,std::ios::binary);
45- if (!input) {
43+ // A RECORD THAT EXISTS AND CANNOT YET BE OPENED IS BEING PUBLISHED. On Windows
44+ // the name a rename installs can be refused to a reader for a short interval
45+ // (the rename's own handle, or a scanner holding the new file); a reader that
46+ // took that refusal as the record's content declared a live compiler lost
47+ // (mcpp-community/mcpp#889, measured 10 of 40 Windows cross builds). The open
48+ // is retried with backoff for at most kRecordSettle; a name that is absent is
49+ // absent at once, and a refusal that outlasts the bound is still an error.
50+ inline constexpr auto kRecordSettle =std::chrono::milliseconds(250 );
51+ enum class Opened { yes, absent, refused };
52+ // Opens `path' for reading, waiting out a publication in progress.
53+ Opened open_settled (std::ifstream& input,const fs::path& path) {
54+ input.open (path,std::ios::binary);
55+ if (input)return Opened::yes;
56+ const auto limit=std::chrono::steady_clock::now ()+kRecordSettle ;
57+ auto pause=std::chrono::milliseconds (1 );
58+ for (;;) {
4659 std::error_code ec;
47- if (!fs::exists (path,ec) && !ec) return std::optional<Fields>{};
48- if (ec)return std::unexpected (" cannot read compiler transaction record" );
49- // An atomic publisher may install the record between the failed open
50- // and the existence probe. Retry once; a persistent refusal still
51- // fails rather than being mistaken for an absent record.
60+ const bool present=fs::exists (path,ec);
61+ if (!present&&!ec)return Opened::absent;
62+ input.clear ();
5263 input.open (path,std::ios::binary);
53- if (!input)return std::unexpected (" cannot read compiler transaction record" );
64+ if (input)return Opened::yes;
65+ if (std::chrono::steady_clock::now ()>=limit)return Opened::refused;
66+ std::this_thread::sleep_for (pause);
67+ pause=std::min (pause*2 ,std::chrono::milliseconds (32 ));
5468 }
69+ }
70+ Record read (const fs::path& path, std::string_view tag) {
71+ std::ifstream input;
72+ const auto opened=open_settled (input,path);
73+ if (opened==Opened::absent)return std::optional<Fields>{};
74+ if (opened==Opened::refused)return std::unexpected (" cannot read compiler transaction record" );
5575 std::string bytes (65537 ,' \0 ' );
5676 input.read (bytes.data (),bytes.size ());bytes.resize (static_cast <std::size_t >(input.gcount ()));
5777 if (input.bad ())return std::unexpected (" cannot read compiler transaction record" );
@@ -118,7 +138,11 @@ bool copy_atomic(const fs::path& from,const fs::path& to) {
118138 fs::copy_file (from,temp,fs::copy_options::overwrite_existing,ec);if (ec)return false ;
119139 const auto time=fs::last_write_time (from,ec);if (ec)return false ;
120140 fs::last_write_time (temp,time,ec);if (ec)return false ;
121- return bool (os::fs::rename (temp,to,true ));
141+ // The host namespace, as for every record of this module (the copy was
142+ // written through it); a name in Windows' long spelling stays valid.
143+ fs::rename (temp,to,ec);
144+ if (ec) {fs::remove (temp,ec);return false ;}
145+ return true ;
122146}
123147struct Attempt {
124148 fs::path directory, bmi, object, slot, dep;
@@ -299,17 +323,26 @@ bool release_reservation(const fs::path& reservation,const fs::path& attempt) {
299323 if ((**current)[0 ]!=spelling (attempt))return true ; // a later, separately locked slot
300324 std::error_code ec;fs::remove (reservation,ec);return !ec;
301325}
302- bool controller_alive (const Attempt& attempt) {
326+ // WHETHER A CONTROLLER IS RUNNING IS A QUESTION ABOUT A PROCESS, NOT ABOUT A
327+ // FILE. Only an identity that names an ended process answers `ended'; a record
328+ // that cannot be read yet answers `unknown', and the caller keeps waiting
329+ // within its own bounds. A record that is absent is the existing startup case.
330+ enum class Controller { running, ended, unknown };
331+ Controller controller_state (const Attempt& attempt) {
303332 auto controller=read (attempt.directory /" controller" ," mcpp-controller-v1" );
304- if (!controller)return false ;
305- if (!*controller)return true ; // the existing startup bound applies
306- if ((**controller).size ()!=3 )return false ;
333+ if (!controller)return Controller::unknown ;
334+ if (!*controller)return Controller::running ; // the existing startup bound applies
335+ if ((**controller).size ()!=3 )return Controller::ended ;
307336 os::process::program who{};const auto & fields=**controller;
308337 const auto id=std::from_chars (fields[0 ].data (),fields[0 ].data ()+fields[0 ].size (),who.id );
309338 const auto birth=std::from_chars (fields[1 ].data (),fields[1 ].data ()+fields[1 ].size (),who.started_ns );
310- return id.ec ==std::errc{}&&id.ptr ==fields[0 ].data ()+fields[0 ].size ()
311- &&birth.ec ==std::errc{}&&birth.ptr ==fields[1 ].data ()+fields[1 ].size ()
312- &&who.id &&who.started_ns &&os::process::alive (who);
339+ const bool valid=id.ec ==std::errc{}&&id.ptr ==fields[0 ].data ()+fields[0 ].size ()
340+ &&birth.ec ==std::errc{}&&birth.ptr ==fields[1 ].data ()+fields[1 ].size ()&&who.id &&who.started_ns ;
341+ if (!valid)return Controller::ended;
342+ return os::process::alive (who) ? Controller::running : Controller::ended;
343+ }
344+ std::string_view describe (Controller c) {
345+ return c==Controller::running ? " running" : c==Controller::ended ? " ended" : " unknown" ;
313346}
314347
315348std::expected<std::string,std::string> receive (os::ipc::connection& connection,std::chrono::milliseconds bound) {
@@ -399,7 +432,9 @@ private:
399432 if (*retired) {it=held_.erase (it);continue ;}
400433 auto attempt=Attempt::load (directory);
401434 if (!attempt) {fail (attempt.error ());++it;continue ;}
402- if (!controller_alive (*attempt)) {
435+ const auto state=controller_state (*attempt);
436+ if (state==Controller::unknown) {++it;continue ;} // asked again at the next audit
437+ if (state==Controller::ended) {
403438 // Retirement can be published between the first read and the
404439 // controller's exit. Observe it again before reporting loss.
405440 retired=read (directory/" retired" ," mcpp-retired-v1" );
@@ -430,7 +465,9 @@ private:
430465 const auto actor=named ((**unit)[3 ]);
431466 if (actor.parent_path ()!=generation_/" actors" ) {send (connection," REFUSED" );continue ;}
432467 auto lease=os::fs::lock::acquire (actor,os::fs::lock::kind::exclusive,false );
433- if (lease||lease.error ().code !=os::errc::again||!controller_alive (*prepared)) {send (connection," REFUSED" );continue ;}
468+ // The actor's lock is held, so its controller is running; a record
469+ // still settling does not refuse the registration.
470+ if (lease||lease.error ().code !=os::errc::again||controller_state (*prepared)==Controller::ended) {send (connection," REFUSED" );continue ;}
434471 auto retained=os::process::unit_observer::open ((**unit)[0 ]);
435472 if (!retained) {send (connection," REFUSED" );continue ;}
436473 std::lock_guard guard (admission_);
0 commit comments