0.11.0 — what openkal 0.10 made possible, and two of them were recorded as impossible one release ago - #24
Merged
Merged
Conversation
…rded as
impossible one release ago
⭐⭐ TWO ENTRIES IN THIS PACKAGE'S OWN DIVERGENCE TABLE ARE DELETED RATHER THAN
AMENDED, AND THEY ARE THE TWO IT SAID COULD NOT BE FIXED HERE.
--- a lock now excludes -------------------------------------------------------
0.10.0 refused `fcntl(F_SETLK)' with ENOSYS, and said the refusal was TEMPORARY
in a way `chmod' is not: every environment beneath openkal can lock a byte range
and what was missing was a WORD. openkal 0.10 is that word.
Measured, with the host as control, and it is the reading that used to be the
other way round:
parent F_SETLK(F_WRLCK) -> 0 ACQUIRED
child F_SETLK(F_WRLCK) -> -1 EAGAIN
=> only one holds it
⭐ AND THE OBSERVATION THAT TELLS THE TWO FORMS APART NEEDS NO SECOND PROGRAM:
a SECOND OPEN FILE of one name, in this program, is refused. The process-held
form would grant it --- the holder being the process, which already holds it ---
and a library that opened one file twice would destroy its own lock. openkal
states the holder as the open FILE, and `examples/surface' now asserts exactly
that.
⚠️ `F_GETLK' IS STILL REFUSED, AND THAT IS NOT AN OVERSIGHT. It asks whether a
lock WOULD block without taking one, and openkal has no operation that answers a
question without performing it --- the absence clause 6.3 records for readiness.
Taking the lock and releasing it would answer, and would also take a lock the
caller did not ask for, hand a spurious `no' to a caller that already holds one,
and be stale on return.
--- and `kill' now reaches the program ----------------------------------------
`execve' is composed as starting a program and ending with its status, so there
are three images where a system with the operation has two. A signal aimed at the
identifier the caller holds reached the WAITER: identical status words, opposite
outcomes, the caller told the program died while the program ran to completion.
0.10.0 recorded this as NOT fixable here and told callers to use `posix_spawn'.
openkal 0.10's `kal_process_spawn_bound' says the thing that could not be said,
and `execve' --- and only `execve' --- asks for it. `posix_spawn' means the
opposite: a POSIX child outlives its parent.
fork + execve status=0x000f program was killed
posix_spawn status=0x000f program was killed
⚠️ A backend may decline the binding, and then this falls back to the unbound
spawn rather than refusing to start the program. An `execve' whose program is
unbound is where this port has always been; an `execve' that starts nothing is
worse.
--- three more that were absent -----------------------------------------------
`sched_getaffinity' answered ENOSYS, so `sysconf(_SC_NPROCESSORS_ONLN)' fell back
to 1 and `hardware_concurrency()' answered 1 SILENTLY. Now 32 on a machine with
32. ⚠️ Zero from the enquiry is reported as a refusal rather than as a bitmap of
one processor: "cannot say" is not "one", and musl would read the latter as a
fact this port had invented.
`statfs' had nothing beneath it, so `std::filesystem::space' reported ENOSYS.
`utimensat' upon a DIRECTORY reached it by opening the directory for READING and
setting the time on that --- which worked and was outside anything the interface
stated. It now takes `kal_fs_set_modified_at', which is the stated route. The old
way is kept for a backend that has not followed, and is tried only where the new
operation is absent.
--- and one thing that did NOT change -----------------------------------------
⚠️ `l_whence == SEEK_END' is refused. openkal takes a position and a length; the
length a range would be measured back from is not something the interface reports
at the moment the lock is taken, and computing it from a size that may already
have changed is the kind of answer this port exists to refuse.
⚠️ ⚠️ TWO ROWS ARE DELETED RATHER THAN AMENDED, AND THEY ARE THE TWO THIS PACKAGE RECORDED AS NOT FIXABLE HERE. The lock row said its refusal was TEMPORARY in a way the permission row is not, and that when the specification gained the word the row would change. It has. The `execve' paragraph said a `kill' reaching the waiting copy was 'not answered' and told callers to use `posix_spawn' instead; `kal_process_spawn_bound' answers it and the advice is withdrawn. ⭐ A record that says what WOULD change it, and then changes when that happens, is the difference between a divergence table and a list of complaints. Both rows named the thing they were waiting for. Volume capacity leaves the 'no operation beneath' row; hard links, named pipes and a bidirectional pair stay in it, because openkal still has no operation for those and this package is not going to invent one.
⭐ A CONSEQUENCE OF THE SPECIFICATION ADDITION THAT THE ADDITION ITSELF DID NOT PREDICT, AND CI FOUND IT. The matrix row for that system carried `--no-dir-time' because `kal_fs_open' there names `FILE_NON_DIRECTORY_FILE' --- correctly, since it opens a FILE --- so the route 0.10.0 used (open the directory for reading, stamp that) could not work. openkal 0.10's `kal_fs_set_modified_at' takes a NAME, and the Windows implementation opens for the ATTRIBUTE alone. That reaches a directory. So the observation that asserted a refusal there is now asserting something false, and the row flips to `--dir-time'.⚠️ The divergence this README row recorded was caused by a MISSING DECLARATION, not by a property of that system --- which is why it went away when the declaration arrived rather than being worked around.
⚠️ ⚠️ THE ELEVENTH REPLACED SOURCE, AND THE THIRD OF A KIND THIS PORT ALREADY NAMES TWICE. unsigned long arg; arg = va_arg(ap, unsigned long); case F_SETLK: return syscall(SYS_fcntl, fd, cmd, (void *)arg); An `unsigned long' holds a pointer on every system musl was written for and thirty-two bits on one this port builds for, so a `struct flock *' arrived with its top half discarded BEFORE THIS PORT SAW IT. ⭐ IT WAS UNREACHABLE UNTIL THIS RELEASE, which is why it survived three of them. Every command answered before took an integer or took a pointer nothing followed: `F_SETLK' returned 0 and did nothing, then reported ENOSYS. A truncated pointer that nothing dereferences is a truncated pointer nothing reports. openkal 0.10 gave this port a real lock, and it faulted on the first attempt.⚠️ Measured under an emulator of that system, and the register file names the TYPE rather than the symptom: page fault on read access to 0x00000000fe2ffec2 rax:00000000fe2ffec0 rsp:00007ffffe2fc7a0 movzxw 0x02(%rax), %eax `rax' is the caller's pointer with its top half gone; the offset it faults at is `l_whence', the first field this port reads.⚠️ AND THE VARARG TYPE IS THE CALLING CONVENTION, NOT A DETAIL. `va_arg(ap, unsigned long)' and `va_arg(ap, uintptr_t)' read different numbers of bytes where the two differ, so a cast afterwards is too late. ⭐ Both macOS cross lists carry the new exclusion in the same commit. That is the lesson of the tenth entry, which they did not carry and which the cross-link job reported as a duplicate symbol.
The project behind openkal-linux#13 is local now, so this is measured against it rather than against probes. ⭐ CROSS-COMPILATION IS THROUGH THE NATIVE PATH FOR BOTH ARCHITECTURES, and their `scripts/build-static.sh' --- which opens by saying it bypasses mcpp's musl target --- can retire. x86_64 and aarch64 both link statically, zero INTERP, zero undefined symbols. aarch64 was not a compilation problem: three lines were missing from the manifest.⚠️ ⚠️ `chdir' DOES NOT REACH A STARTED PROGRAM, measured with a host as control. This library's `chdir' rebinds its own table because openkal has no operation that changes a running program's working directory, and the backend's `execveat' uses its directory only to RESOLVE THE NAME. So a started program runs where the whole program was started, whatever the caller did --- and openkal-linux's own comment claims the opposite property in as many words.⚠️ THERE ARE NO PROCESS GROUPS, so a timeout cannot kill a tree. Their logic is the standard one and the direct child does die; what survives is what bash backgrounded. This is the alternative I rejected when designing `kal_process_spawn_bound' --- the rejection still holds, but it shows that killing a TREE is a thing consumers need and that the bound spawn answers only the first layer. ⭐ None of the three is in issue 13's list, and none is in any criterion I added this round. A criterion is written against a defect already known; a consumer's tests are written against what they are trying to do.
…at a time⚠️ ⚠️ MEASURED AGAINST THE HOST, ON `echo one; sleep 0.4; echo two': here "o" "n" "e" "." "t" "w" "o" "." eight chunks host "one." "two." two openkal has no operation reporting whether a transfer would proceed, so a readiness enquiry TAKES A BYTE to make its answer true, and `read' delivers it. Returning only that byte is a legal short read --- and a caller that polls goes straight back to `poll', which takes another byte. So the whole of a stream arrives ONE BYTE PER ITERATION, for ever. ⭐ EVERY BYTE IS DELIVERED AND IN ORDER, WHICH IS WHY IT SURVIVED. A caller that concatenates sees exactly the right bytes; the defect is invisible to anyone who does not look at the BOUNDARIES. A caller that scans a chunk for a word finds none, because `two' arrives as `t' and `wo'. ⭐⭐ AND IT IS WHAT openkal-linux#13's FIRST REPORT MEANT BY "only output one byte". That line has been in the issue since the beginning and nothing explained it. This is it. ⇒ The remedy is the operation the enquiry is built on: a bound of `now' asks for whatever has already arrived and does not wait, so the byte and the rest of what is there come back together. Where the environment declines `openkal.timeout' there is no such operation and the single byte is what can be honestly returned. Measured through the reporter's own suite rather than a probe: their `test_process_stream' had two observations red, and both are green --- `增量: 两段各自到达' and `增量: 第二段晚于第一段 ≥200ms(真流式)', the second of which their run had never even reached.
…0.11 The one-byte read is fixed and §7.4 now says what it was: `poll' held a single byte, so a polled stream arrived a byte at a time --- which is what openkal-linux#13's "only one byte" meant. Nothing was truncated; I had read the report wrong. §7.5 is new: aarch64 had never been run at all, and the reason was three lines of manifest rather than anything about the architecture. A `cfg` predicate covers both, the artefacts are static, and the host's binfmt_misc runs them --- no virtual machine. Both architectures fail identically, which is itself a reading. The 0.11 note is a proposal and nothing more. Two gaps arrived together --- saying where a program runs, and ending what it started --- and `process.h' already warned where a variant-per-combination ends. It states its own cost: one general form makes three existing declarations redundant, and clause 8 will not remove them.
…ueue answered ENOSYS and musl does not check `FUTEX_REQUEUE' asks for waiters upon one address to be MOVED to another, so a context released by a condition variable goes straight to waiting upon the mutex instead of waking only to block again. openkal has kal_task_wait and kal_task_wake and nothing that moves a waiter between addresses, so this reached the default arm and answered ENOSYS. musl's `unlock_requeue' releases the barrier and then makes that request. When it fails there is no remaining path that wakes anybody, so the next waiter sleeps until the program is killed. ⭐ IT TAKES TWO WAITERS, WHICH IS WHY IT SURVIVED. The call is reached only when a second context is queued behind the one being released; one waiter upon a condition variable never reaches it, and one waiter is what almost every program has. A consumer's test that has two contexts wait upon one variable passes on a host in 0.06s and did not finish here in 300 --- that pair of numbers is the whole diagnosis, and it is why the host control matters. Waking is a correct substitute for moving: a waiter is always inside `while (a_cas(l, 0, 2))', so a context woken upon the source address re-reads the word, takes the lock the caller has just released, and proceeds. What is lost is one journey through the scheduler, not an outcome. Measured after: the same test, 300s timeout to 0.10s.
…ays was This file starts four contexts and contends a mutex 80000 times, and it passed throughout a defect that stopped the port dead. Contending a mutex is not waiting upon a condition variable, and one waiter is not two: musl asks for a requeue only when a second context is queued behind the one being released, so a port that cannot requeue costs nothing until a program has two waiters. ⇒ So the observation is not "a condition variable works". It is that the second waiter is woken. It also checks that both were waiting BEFORE the broadcast, because a broadcast that arrives first wakes nobody and the observation would then hold for a reason unrelated to what it checks. Verified in both directions, which is what makes it a guard rather than a line that happens to pass: with the requeue case it reports three more `ok's, and with that case removed the probe stops at exactly this block --- "it did not return; where it was", immediately after counter=80000.⚠️ Written at file scope. The obvious spelling puts the waiter beside the two statics inside the block, and a nested function is a GCC extension that the llvm half of this repository's CI rejects.
…re written Four defects came out of the consumer's own tests. Two are gaps in the specification (7.2, 7.3) and two are this port's use of primitives it already had (7.4, 7.6) --- and the second pair is the interesting half, because nothing in the specification had to change for either. 7.6 is the worst of the four and the one the criteria had the least chance of finding. This repository's own probe starts four contexts and contends a mutex eighty thousand times, and passed throughout: contending a mutex is not waiting upon a condition variable, and one waiter is not two. The requeue is requested only when a second context is queued behind the one being released. ⇒ Records the three pieces of evidence that agree (the ENOSYS arm, musl's unchecked `unlock_requeue', and a futex value of 2 that names the exact loop), and the pair of numbers that made it a diagnosis rather than a guess: 0.06s on a host, unfinished in 300 here.
…changes Both architectures now agree test by test: 96 pass and 12 fail on each, and the twelve are eight the project owns and four that are the two remaining specification gaps. Every attribution has a discriminator behind it rather than a name that sounds like one --- the same suite run against the host target, which is what separated test_chat_approval (green there, 300s here, so ours) from test_chat_live (red there too, so not).
The one-byte fix landed with no observation of its own --- I wrote it up in the plan as though `examples/subprocess' guarded it, and it did not. Corrected by adding the guard rather than by softening the claim. ⭐ It deliberately does not check the bytes. Every byte was always delivered and in order, which is exactly why the defect survived: a caller that concatenates sees the right output, and only a caller that looks at the BOUNDARIES sees anything wrong. So the observation is that the FIRST arrival is the segment the writer wrote, and that the second arrives separately rather than folded into it --- a reader that got all eight bytes at once would satisfy the first check for the wrong reason. Verified in both directions: with the fix both observations hold, and against the commit before it the probe prints "first arrival was 1 byte(s)" and fails both. It also prints that number, so a future failure says what it saw. ⇒ §5 gains criteria 8 and 9, and says why neither could have come from the method that produced 1 to 7: those were written from what the specification added and from what I might have broken, and not one of them from what a consumer actually does.
The table says what this port cannot do, and the two things the consumer in openkal-linux#13 actually ran into were missing from it: the working directory a started program runs in, and terminating what a started program itself started. Both were known, analysed and written up in .agents/docs --- and absent from the file a consumer reads.⚠️ The first deserves the emphasis it now has, because it is SILENT. `chdir' reports success, this program's own paths follow it, and only the started program disagrees; there is no error anywhere to notice. Both rows say why the port cannot compose the answer, so that "openkal has no operation for it" is not read as "the port did not bother": a working directory cannot be set in a copy because openkal deliberately has no operation that moves a running program's, and a lock built beside the file would be released by nobody. And both name where the addition is proposed rather than leaving the reader to wonder whether anyone noticed.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the openkal 0.10 move: mcpplibs/openkal#25.
⭐⭐ Two entries in this package's own divergence table are deleted rather than amended, and they are the two it recorded as not fixable here.
A lock now excludes
0.10.0 refused
fcntl(F_SETLK)withENOSYSand said the refusal was temporary in a way the permission row is not. openkal 0.10 is the word that was missing.⭐ And the observation that tells the two lock forms apart needs no second program: a second OPEN FILE of one name, in this program, is refused. The process-held form grants it, and a library that opened one file twice would destroy its own lock.
F_GETLKis still refused — it asks whether a lock would block without taking one, and openkal has no operation that answers a question without performing it.And
killnow reaches the programexecve— and onlyexecve— asks forkal_process_spawn_bound.posix_spawnmeans the opposite: a POSIX child outlives its parent.Three more that were absent
sched_getaffinityanswered ENOSYS, sohardware_concurrency()answered 1 silently; now 32 on a machine with 32.statfshad nothing beneath it.utimensaton a directory took a route outside anything the interface stated, and now takes the stated one.