Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.
openkal = "0.17.1"

[target.'cfg(windows)'.dependencies]
openkal-windows = "0.13.2"
openkal-windows = "0.13.3"
```

Its purpose is as much to test the specification as to be used. openkal was
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-windows"
version = "0.13.2"
version = "0.13.3"
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
license = "Apache-2.0"

Expand Down
23 changes: 21 additions & 2 deletions src/env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,28 @@ const char* store(const wchar_t* w, int wlen, okw_uptr& out_len) {

int wide_length(const wchar_t* w) { int n = 0; while (w && w[n]) ++n; return n; }

// ONCE, AND NOT TWICE AT ONCE (0.13.3). The first callers of two threads met
// here: one marked the tables ready and filled them while the other read them
// empty, so a
// variable that was set --- PATH --- was reported absent to a program that
// asked from two threads at the start. Measured in mcpp, whose two threads
// starting `hostname' and `ping' found one of them "not on PATH". The filling
// happens under a slim lock, and every caller takes it, which also orders its
// reads after the filling.
SRWLOCK_ g_prepare_lock{};

void fill();

void prepare() {
if (g_ready) return;
g_ready = true;
AcquireSRWLockExclusive(&g_prepare_lock);
if (!g_ready) {
fill();
g_ready = true;
}
ReleaseSRWLockExclusive(&g_prepare_lock);
}

void fill() {
int count = 0;
wchar_t** parts = CommandLineToArgvW(GetCommandLineW(), &count);
if (parts) {
Expand Down Expand Up @@ -99,6 +117,7 @@ void prepare() {
}
FreeEnvironmentStringsW(block);
}

}

bool same(const char* a, okw_uptr alen, const char* b, okw_uptr blen) {
Expand Down
21 changes: 18 additions & 3 deletions src/timeout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,25 @@ int await(SOCKET s, short events, kal_u64 ns) {
// `ERROR_BROKEN_PIPE', and a read that follows reports the end of input without
// waiting --- which is what readiness asserts. Reporting the failure here would
// make a program that reads until end-of-input wait for ever instead.
//
// THE BOUND IS MEASURED, NOT COUNTED (0.13.3). The loop counted its sleeps as
// milliseconds, and `Sleep(1)' sleeps until the next tick of the system's timer
// --- 15.6 ms by default --- so a 100 ms bound waited about a second and a
// half. Measured through mcpplibs.os: a program whose bounded read of 100 ms
// should have expired received input written 400 ms after it began. The time
// passed is read from the performance counter.
int await_pipe(HANDLE h, kal_u64 ns) {
const int ms = bound_ms(ns);
kal_u64 waited = 0;
LARGE_INTEGER frequency{}, started{};
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&started);
auto waited_ms = [&]() -> kal_u64 {
LARGE_INTEGER now{};
QueryPerformanceCounter(&now);
if (frequency.QuadPart <= 0) return static_cast<kal_u64>(ms);
return static_cast<kal_u64>(now.QuadPart - started.QuadPart) * 1000u
/ static_cast<kal_u64>(frequency.QuadPart);
};
for (;;) {
DWORD available = 0;
if (!PeekNamedPipe(h, nullptr, 0, nullptr, &available, nullptr)) {
Expand All @@ -131,11 +147,10 @@ int await_pipe(HANDLE h, kal_u64 ns) {
}
if (available > 0) return kal_ok;
if (ms == 0) return kal_err_again;
if (ms > 0 && waited >= static_cast<kal_u64>(ms)) return kal_err_again;
if (ms > 0 && waited_ms() >= static_cast<kal_u64>(ms)) return kal_err_again;
// The interval this interface reports as its granularity, so the cost of
// the loop is the number already stated rather than a second one.
Sleep(1);
waited += 1;
}
}

Expand Down
39 changes: 39 additions & 0 deletions tests/bounded_read_of_an_idle_pipe.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// A bounded read of a pipe with nothing in it expires near its bound (0.13.3).
//
// The wait counted its sleeps of one millisecond as milliseconds, and a sleep
// of one millisecond lasts until the next tick of the system's timer --- 15.6
// ms by default --- so a 100 ms bound waited about a second and a half. The
// read expires here within four times its bound, which a counted loop on a
// default timer does not.
#include <cstdio>
#include <openkal/process.h>
#include <openkal/stream.h>
#include <openkal/time.h>
#include <openkal/timeout.h>
#include <openkal/types.h>

int main() {
kal_stream mine{}, theirs{};
if (kal_process_channel(&mine, &theirs) != kal_ok) {
std::printf("FAIL: a channel is made\n");
return 1;
}
char byte = 0;
const kal_duration started = kal_time_monotonic();
const kal_intptr n = kal_timeout_read(mine, &byte, 1, 100000000ull /* 100 ms */);
const kal_duration took = kal_time_monotonic() - started;
kal_process_channel_close(theirs);
kal_process_channel_close(mine);
int failures = 0;
if (n != -kal_err_again) {
std::printf("FAIL: a bounded read of an idle pipe expires (it answered %lld)\n", static_cast<long long>(n));
++failures;
}
if (took > 400000000ull) {
std::printf("FAIL: it expires near its bound (100 ms asked, %llu ms taken)\n",
static_cast<unsigned long long>(took / 1000000ull));
++failures;
}
if (failures == 0) std::printf("openkal-windows: a bounded read of an idle pipe expires near its bound\n");
return failures == 0 ? 0 : 1;
}
78 changes: 78 additions & 0 deletions tests/environment_read_from_two_threads.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// The environment read first from two contexts at once (0.13.3).
//
// The tables behind openkal.env are filled on the first enquiry. Two contexts
// that asked at the same moment met there: one marked the tables ready and
// filled them while the other read them empty, and a variable that was set
// --- PATH --- was reported absent. The first enquiry is made here from two
// contexts released together, in a fresh copy of this program each round, and
// both are told the variable.
#include <cstdio>
#include <cstring>
#include <openkal/env.h>
#include <openkal/fs.h>
#include <openkal/process.h>
#include <openkal/task.h>
#include <openkal/types.h>

namespace {

volatile kal_u32 g_gate = 0;
volatile int g_found[2] = { 0, 0 };

void ask(void* p) {
const int i = static_cast<int>(reinterpret_cast<kal_uintptr>(p));
while (g_gate == 0) {}
char value[4096];
g_found[i] = kal_env_var("PATH", 4, value, sizeof value) > 0 ? 1 : 0;
}

// The copy: both contexts ask for PATH first thing; 0 when both were told.
int copy() {
kal_task a{}, b{};
if (kal_task_start(ask, reinterpret_cast<void*>(kal_uintptr{ 0 }), &a) != kal_ok) return 3;
if (kal_task_start(ask, reinterpret_cast<void*>(kal_uintptr{ 1 }), &b) != kal_ok) return 3;
g_gate = 1;
kal_task_join(a);
kal_task_join(b);
return g_found[0] && g_found[1] ? 0 : 1;
}

} // namespace

int main(int argc, char** argv) {
if (argc > 1 && std::strcmp(argv[1], "--copy") == 0) return copy();

char self[1024];
const kal_intptr n = kal_env_image(self, sizeof self - 1);
if (n <= 0) { std::printf("FAIL: this program's image is named\n"); return 1; }
self[n] = 0;
// `C:\...\x.exe' named relative to the volume the image is on.
kal_dir volume{};
for (kal_uintptr i = 0; i < kal_fs_preopen_count(); ++i) {
char name[16]{}; kal_uintptr len = 0; kal_dir d{};
if (kal_fs_preopen(i, &d, name, sizeof name, &len) != kal_ok) continue;
if (len == 2 && (name[0] | 0x20) == (self[0] | 0x20) && name[1] == ':') { volume = d; break; }
}
if (!volume.h) { std::printf("openkal-windows: the image's volume is not granted; nothing to observe\n"); return 0; }
const char* path = self + 3; // past `C:\'
int failures = 0;
for (int round = 0; round < 20; ++round) {
const char* args[2] = { self, "--copy" };
const kal_uintptr lens[2] = { std::strlen(self), 6 };
const kal_spawn how{ volume, volume, nullptr, nullptr, 0, 0 };
kal_process p{};
if (kal_process_spawn(&how, path, std::strlen(path), args, lens, 2, nullptr, nullptr, 0, nullptr, &p) != kal_ok) {
std::printf("FAIL: a copy starts\n");
return 1;
}
int status = -1, terminated = 0;
kal_process_wait(p, &status, &terminated);
if (status != 0) ++failures;
}
if (failures) {
std::printf("FAIL: in %d of 20 copies a context read the environment before it was filled\n", failures);
return 1;
}
std::printf("openkal-windows: the environment read first from two contexts at once\n");
return 0;
}
Loading