mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-18 04:20:28 +00:00
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -156,18 +156,14 @@ jobs:
|
||||
|
||||
- name: Check benchmarks
|
||||
run: ./odin check tests/benchmark -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
|
||||
|
||||
- name: Odin check examples/all for Linux i386
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
|
||||
- name: Odin check examples/all for Linux arm64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
|
||||
- name: Odin check examples/all for FreeBSD amd64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
|
||||
- name: Odin check examples/all for OpenBSD amd64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
|
||||
|
||||
- name: Odin check examples/all for js_wasm32
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm32
|
||||
@@ -178,12 +174,6 @@ jobs:
|
||||
- name: Odin check examples/all/sdl3 for Linux i386
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_i386
|
||||
- name: Odin check examples/all/sdl3 for Linux arm64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_arm64
|
||||
- name: Odin check examples/all/sdl3 for FreeBSD amd64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:freebsd_amd64
|
||||
- name: Odin check examples/all/sdl3 for OpenBSD amd64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:openbsd_amd64
|
||||
|
||||
@@ -360,7 +360,7 @@ new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator,
|
||||
|
||||
@(builtin, require_results)
|
||||
new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error {
|
||||
t = (^T)(raw_data(mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return))
|
||||
t = (^T)(raw_data(mem_alloc_non_zeroed(size_of(T), align_of(T), allocator, loc) or_return))
|
||||
if t != nil {
|
||||
t^ = data
|
||||
}
|
||||
|
||||
140
core/container/pool/pool.odin
Normal file
140
core/container/pool/pool.odin
Normal file
@@ -0,0 +1,140 @@
|
||||
package container_pool
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:sanitizer"
|
||||
|
||||
import "core:mem"
|
||||
import "core:sync"
|
||||
|
||||
_ :: sanitizer
|
||||
|
||||
DEFAULT_BLOCK_SIZE :: _DEFAULT_BLOCK_SIZE
|
||||
|
||||
Pool_Arena :: _Pool_Arena
|
||||
|
||||
/*
|
||||
A thread-safe (between init and destroy) object pool backed by virtual growing arena returning stable pointers.
|
||||
The element type requires an intrusive link node.
|
||||
|
||||
Example:
|
||||
Elem :: struct {
|
||||
link: ^Elem,
|
||||
}
|
||||
|
||||
p: pool.Pool(Elem)
|
||||
pool.init(&p, "link")
|
||||
*/
|
||||
Pool :: struct($T: typeid) {
|
||||
arena: Pool_Arena,
|
||||
num_outstanding: int,
|
||||
num_ready: int,
|
||||
link_off: uintptr,
|
||||
free_list: ^T,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
init :: proc(p: ^Pool($T), $link_field: string, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: mem.Allocator_Error)
|
||||
where intrinsics.type_has_field(T, link_field),
|
||||
intrinsics.type_field_type(T, link_field) == ^T {
|
||||
p.link_off = offset_of_by_string(T, link_field)
|
||||
return _pool_arena_init(&p.arena, block_size)
|
||||
}
|
||||
|
||||
destroy :: proc(p: ^Pool($T)) {
|
||||
elem := sync.atomic_exchange_explicit(&p.free_list, nil, .Acquire)
|
||||
|
||||
sync.atomic_store_explicit(&p.num_ready, 0, .Relaxed)
|
||||
|
||||
when .Address in ODIN_SANITIZER_FLAGS {
|
||||
for ; elem != nil; elem = _get_next(p, elem) {
|
||||
_unpoison_elem(p, elem)
|
||||
}
|
||||
} else {
|
||||
_ = elem
|
||||
}
|
||||
|
||||
_pool_arena_destroy(&p.arena)
|
||||
p.arena = {}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get :: proc(p: ^Pool($T)) -> (elem: ^T, err: mem.Allocator_Error) #optional_allocator_error {
|
||||
defer sync.atomic_add_explicit(&p.num_outstanding, 1, .Relaxed)
|
||||
|
||||
for {
|
||||
elem = sync.atomic_load_explicit(&p.free_list, .Acquire)
|
||||
if elem == nil {
|
||||
// NOTE: pool arena has an internal lock.
|
||||
return new(T, _pool_arena_allocator(&p.arena))
|
||||
}
|
||||
|
||||
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, elem, _get_next(p, elem), .Acquire, .Relaxed); ok {
|
||||
_set_next(p, elem, nil)
|
||||
_unpoison_elem(p, elem)
|
||||
sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
put :: proc(p: ^Pool($T), elem: ^T) {
|
||||
mem.zero_item(elem)
|
||||
_poison_elem(p, elem)
|
||||
|
||||
defer sync.atomic_sub_explicit(&p.num_outstanding, 1, .Relaxed)
|
||||
defer sync.atomic_add_explicit(&p.num_ready, 1, .Relaxed)
|
||||
|
||||
for {
|
||||
head := sync.atomic_load_explicit(&p.free_list, .Relaxed)
|
||||
_set_next(p, elem, head)
|
||||
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, head, elem, .Release, .Relaxed); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
num_outstanding :: proc(p: ^Pool($T)) -> int {
|
||||
return sync.atomic_load(&p.num_outstanding)
|
||||
}
|
||||
|
||||
num_ready :: proc(p: ^Pool($T)) -> int {
|
||||
return sync.atomic_load(&p.num_ready)
|
||||
}
|
||||
|
||||
cap :: proc(p: ^Pool($T)) -> int {
|
||||
return sync.atomic_load(&p.num_ready) + sync.atomic_load(&p.num_outstanding)
|
||||
}
|
||||
|
||||
_get_next :: proc(p: ^Pool($T), elem: ^T) -> ^T {
|
||||
return (^^T)(uintptr(elem) + p.link_off)^
|
||||
}
|
||||
|
||||
_set_next :: proc(p: ^Pool($T), elem: ^T, next: ^T) {
|
||||
(^^T)(uintptr(elem) + p.link_off)^ = next
|
||||
}
|
||||
|
||||
@(disabled=.Address not_in ODIN_SANITIZER_FLAGS)
|
||||
_poison_elem :: proc(p: ^Pool($T), elem: ^T) {
|
||||
if p.link_off > 0 {
|
||||
sanitizer.address_poison_rawptr(elem, int(p.link_off))
|
||||
}
|
||||
|
||||
len := size_of(T) - p.link_off - size_of(rawptr)
|
||||
if len > 0 {
|
||||
ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr))
|
||||
sanitizer.address_poison_rawptr(ptr, int(len))
|
||||
}
|
||||
}
|
||||
|
||||
@(disabled=.Address not_in ODIN_SANITIZER_FLAGS)
|
||||
_unpoison_elem :: proc(p: ^Pool($T), elem: ^T) {
|
||||
if p.link_off > 0 {
|
||||
sanitizer.address_unpoison_rawptr(elem, int(p.link_off))
|
||||
}
|
||||
|
||||
len := size_of(T) - p.link_off - size_of(rawptr)
|
||||
if len > 0 {
|
||||
ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr))
|
||||
sanitizer.address_unpoison_rawptr(ptr, int(len))
|
||||
}
|
||||
}
|
||||
29
core/container/pool/pool_arena_others.odin
Normal file
29
core/container/pool/pool_arena_others.odin
Normal file
@@ -0,0 +1,29 @@
|
||||
#+build !darwin
|
||||
#+build !freebsd
|
||||
#+build !openbsd
|
||||
#+build !netbsd
|
||||
#+build !linux
|
||||
#+build !windows
|
||||
#+private
|
||||
package container_pool
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:mem"
|
||||
|
||||
_Pool_Arena :: runtime.Arena
|
||||
|
||||
_DEFAULT_BLOCK_SIZE :: mem.Megabyte
|
||||
|
||||
_pool_arena_init :: proc(arena: ^Pool_Arena, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: runtime.Allocator_Error) {
|
||||
runtime.arena_init(arena, block_size, runtime.default_allocator()) or_return
|
||||
return
|
||||
}
|
||||
|
||||
_pool_arena_allocator :: proc(arena: ^Pool_Arena) -> runtime.Allocator {
|
||||
return runtime.arena_allocator(arena)
|
||||
}
|
||||
|
||||
_pool_arena_destroy :: proc(arena: ^Pool_Arena) {
|
||||
runtime.arena_destroy(arena)
|
||||
}
|
||||
24
core/container/pool/pool_arena_virtual.odin
Normal file
24
core/container/pool/pool_arena_virtual.odin
Normal file
@@ -0,0 +1,24 @@
|
||||
#+build darwin, freebsd, openbsd, netbsd, linux, windows
|
||||
package container_pool
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:mem"
|
||||
import "core:mem/virtual"
|
||||
|
||||
_Pool_Arena :: virtual.Arena
|
||||
|
||||
_DEFAULT_BLOCK_SIZE :: mem.Gigabyte
|
||||
|
||||
_pool_arena_init :: proc(arena: ^Pool_Arena, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: runtime.Allocator_Error) {
|
||||
virtual.arena_init_growing(arena, block_size) or_return
|
||||
return
|
||||
}
|
||||
|
||||
_pool_arena_allocator :: proc(arena: ^Pool_Arena) -> runtime.Allocator {
|
||||
return virtual.arena_allocator(arena)
|
||||
}
|
||||
|
||||
_pool_arena_destroy :: proc(arena: ^Pool_Arena) {
|
||||
virtual.arena_destroy(arena)
|
||||
}
|
||||
@@ -91,7 +91,7 @@ destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) {
|
||||
}
|
||||
}
|
||||
|
||||
len :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> (node_count: int) {
|
||||
len :: proc "contextless" (t: $T/Tree($Key, $Value)) -> (node_count: int) {
|
||||
return t._size
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||
}
|
||||
|
||||
// find finds the key in the tree, and returns the corresponding node, or nil iff the value is not present.
|
||||
find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||
find :: proc(t: $T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||
node = t._root
|
||||
for node != nil {
|
||||
switch t._cmp_fn(key, node.key) {
|
||||
@@ -121,7 +121,7 @@ find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||
}
|
||||
|
||||
// find_value finds the key in the tree, and returns the corresponding value, or nil iff the value is not present.
|
||||
find_value :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
|
||||
find_value :: proc(t: $T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
|
||||
if n := find(t, key); n != nil {
|
||||
return n.value, true
|
||||
}
|
||||
@@ -166,7 +166,7 @@ remove :: proc {
|
||||
// removal was successful. While the node's key + value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool {
|
||||
n := find(t, key)
|
||||
n := find(t^, key)
|
||||
if n == nil {
|
||||
return false // Key not found, nothing to do
|
||||
}
|
||||
|
||||
@@ -417,9 +417,10 @@ Create an iterator for traversing the exponential array.
|
||||
|
||||
Example:
|
||||
|
||||
import "lib:xar"
|
||||
import "core:container/xar"
|
||||
import "core:fmt"
|
||||
|
||||
iteration_example :: proc() {
|
||||
iterator_example :: proc() {
|
||||
x: xar.Array(int, 4)
|
||||
defer xar.destroy(&x)
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ NO_CORE_NAMED_TYPES :: #config(ODIN_CORE_FLAGS_NO_CORE_NAMED_TYPES, false)
|
||||
IMPORTING_TIME :: #config(ODIN_CORE_FLAGS_USE_TIME, time.IS_SUPPORTED)
|
||||
|
||||
// Override support for parsing `net` types.
|
||||
// TODO: Update this when the BSDs are supported.
|
||||
IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD)
|
||||
IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD)
|
||||
|
||||
TAG_ARGS :: "args"
|
||||
SUBTAG_NAME :: "name"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
import "core:net"
|
||||
import "core:os"
|
||||
|
||||
Parse_Error_Reason :: enum {
|
||||
@@ -24,6 +26,12 @@ Parse_Error :: struct {
|
||||
message: string,
|
||||
}
|
||||
|
||||
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||
Parse_Error_Reason,
|
||||
runtime.Allocator_Error,
|
||||
net.Parse_Endpoint_Error,
|
||||
}
|
||||
|
||||
// Raised during parsing.
|
||||
// Provides more granular information than what just a string could hold.
|
||||
Open_File_Error :: struct {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#+build netbsd, openbsd
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||
Parse_Error_Reason,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
import "core:net"
|
||||
|
||||
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||
Parse_Error_Reason,
|
||||
runtime.Allocator_Error,
|
||||
net.Parse_Endpoint_Error,
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:net"
|
||||
import "core:os"
|
||||
import "core:reflect"
|
||||
import "core:strconv"
|
||||
@@ -310,7 +311,18 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type:
|
||||
}
|
||||
|
||||
when IMPORTING_NET {
|
||||
if try_net_parse_workaround(data_type, str, ptr, out_error) {
|
||||
if data_type == net.Host_Or_Endpoint {
|
||||
addr, net_error := net.parse_hostname_or_endpoint(str)
|
||||
if net_error != nil {
|
||||
// We pass along `net.Error` here.
|
||||
out_error^ = Parse_Error {
|
||||
net_error,
|
||||
"Invalid Host/Endpoint.",
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
(cast(^net.Host_Or_Endpoint)ptr)^ = addr
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#+private
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
package flags
|
||||
|
||||
import "core:net"
|
||||
|
||||
// This proc exists purely as a workaround for import restrictions.
|
||||
// Returns true if caller should return early.
|
||||
try_net_parse_workaround :: #force_inline proc (
|
||||
data_type: typeid,
|
||||
str: string,
|
||||
ptr: rawptr,
|
||||
out_error: ^Error,
|
||||
) -> bool {
|
||||
if data_type == net.Host_Or_Endpoint {
|
||||
addr, net_error := net.parse_hostname_or_endpoint(str)
|
||||
if net_error != nil {
|
||||
// We pass along `net.Error` here.
|
||||
out_error^ = Parse_Error {
|
||||
net_error,
|
||||
"Invalid Host/Endpoint.",
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
(cast(^net.Host_Or_Endpoint)ptr)^ = addr
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
195
core/nbio/doc.odin
Normal file
195
core/nbio/doc.odin
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
package nbio implements a non-blocking I/O and event loop abstraction layer
|
||||
over several platform-specific asynchronous I/O APIs.
|
||||
|
||||
More examples can be found in Odin's examples repository
|
||||
at [[ examples/nbio ; https://github.com/odin-lang/examples/nbio ]].
|
||||
|
||||
**Event Loop**:
|
||||
|
||||
Each thread may have at most one event loop associated with it.
|
||||
This is enforced by the package, as running multiple event loops on a single
|
||||
thread does not make sense.
|
||||
|
||||
Event loops are reference counted and managed by the package.
|
||||
|
||||
`acquire_thread_event_loop` and `release_thread_event_loop` can be used
|
||||
to acquire and release a reference. Acquiring must be done before any operation
|
||||
is done.
|
||||
|
||||
The event loop progresses in ticks. A tick checks if any work is to be done,
|
||||
and based on the given timeout may block waiting for work.
|
||||
|
||||
Ticks are typically done using the `tick`, `run`, and `run_until` procedures.
|
||||
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "core:nbio"
|
||||
import "core:time"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
err := nbio.acquire_thread_event_loop()
|
||||
assert(err == nil)
|
||||
defer nbio.release_thread_event_loop()
|
||||
|
||||
nbio.timeout(time.Second, proc(_: ^nbio.Operation) {
|
||||
fmt.println("Hellope after 1 second!")
|
||||
})
|
||||
|
||||
err = nbio.run()
|
||||
assert(err == nil)
|
||||
}
|
||||
|
||||
|
||||
**Time and timeouts**:
|
||||
|
||||
Timeouts are intentionally *slightly inaccurate* by design.
|
||||
|
||||
A timeout is not checked continuously, instead, it is evaluated only when
|
||||
a tick occurs. This means if a tick took a long time, your timeout may be ready
|
||||
for a bit of time already before the callback is called.
|
||||
|
||||
The function `now` returns the current time as perceived by the event
|
||||
loop. This value is cached at least once per tick so it is fast to retrieve.
|
||||
|
||||
Most operations also take an optional timeout when executed.
|
||||
If the timeout completes before the operation, the operation is cancelled and
|
||||
called back with a `.Timeout` error.
|
||||
|
||||
|
||||
**Threading**:
|
||||
|
||||
The package has a concept of I/O threads (threads that are ticking) and worker
|
||||
threads (any other thread).
|
||||
|
||||
An I/O thread is mostly self contained, operations are executed on it, and
|
||||
callbacks run on it.
|
||||
|
||||
If you try to execute an operation on a thread that has no running event loop
|
||||
a panic will be executed. Instead a worker thread can execute operations onto
|
||||
a running event loop by taking it's reference and executing operations with
|
||||
that reference.
|
||||
|
||||
In this case:
|
||||
- The operation is enqueued from the worker thread
|
||||
- The I/O thread is optionally woken up from blocking for work with `wake_up`
|
||||
- The next tick, the operation is executed by the I/O thread
|
||||
- The callback is invoked on the I/O thread
|
||||
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "core:nbio"
|
||||
import "core:net"
|
||||
import "core:thread"
|
||||
import "core:time"
|
||||
|
||||
Connection :: struct {
|
||||
loop: ^nbio.Event_Loop,
|
||||
socket: net.TCP_Socket,
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
workers: thread.Pool
|
||||
thread.pool_init(&workers, context.allocator, 2)
|
||||
thread.pool_start(&workers)
|
||||
|
||||
err := nbio.acquire_thread_event_loop()
|
||||
defer nbio.release_thread_event_loop()
|
||||
assert(err == nil)
|
||||
|
||||
server, listen_err := nbio.listen_tcp({nbio.IP4_Any, 1234})
|
||||
assert(listen_err == nil)
|
||||
nbio.accept_poly(server, &workers, on_accept)
|
||||
|
||||
err = nbio.run()
|
||||
assert(err == nil)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, workers: ^thread.Pool) {
|
||||
assert(op.accept.err == nil)
|
||||
|
||||
nbio.accept_poly(op.accept.socket, workers, on_accept)
|
||||
|
||||
thread.pool_add_task(workers, context.allocator, do_work, new_clone(Connection{
|
||||
loop = op.l,
|
||||
socket = op.accept.client,
|
||||
}))
|
||||
}
|
||||
|
||||
do_work :: proc(t: thread.Task) {
|
||||
connection := (^Connection)(t.data)
|
||||
|
||||
// Imagine CPU intensive work that's been ofloaded to a worker thread.
|
||||
time.sleep(time.Second * 1)
|
||||
|
||||
nbio.send_poly(connection.socket, {transmute([]byte)string("Hellope!\n")}, connection, on_sent, l=connection.loop)
|
||||
}
|
||||
|
||||
on_sent :: proc(op: ^nbio.Operation, connection: ^Connection) {
|
||||
assert(op.send.err == nil)
|
||||
// Client got our message, clean up.
|
||||
nbio.close(connection.socket)
|
||||
free(connection)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
**Handle and socket association**:
|
||||
|
||||
Most platforms require handles (files, sockets, etc.) to be explicitly
|
||||
associated with an event loop or configured for non-blocking/asynchronous
|
||||
operation.
|
||||
|
||||
On some platforms (notably Windows), this requires a specific flag at open
|
||||
time (`.Non_Blocking` for `core:os`) and association may fail if the handle was not created
|
||||
correctly.
|
||||
|
||||
For this reason, prefer `open` and `create_socket` from this package instead.
|
||||
|
||||
`associate_handle`, `associate_file`, and `associate_socket` can be used for externally opened
|
||||
files/sockets.
|
||||
|
||||
|
||||
**Offsets and positional I/O**:
|
||||
|
||||
Operations do not implicitly use or modify a handle’s internal file
|
||||
offset.
|
||||
|
||||
Instead, operations such as `read` and `write` are *positional* and require
|
||||
an explicit offset.
|
||||
|
||||
This avoids ambiguity and subtle bugs when multiple asynchronous operations
|
||||
are issued concurrently against the same handle.
|
||||
|
||||
|
||||
**Contexts and callbacks**:
|
||||
|
||||
The `context` inside a callback is *not* the context that submitted the
|
||||
operation.
|
||||
|
||||
Instead, the callback receives the context that was active when the event
|
||||
loop function (`tick`, `run`, etc.) was called.
|
||||
|
||||
This is because otherwise the context would have to be copied and held onto for each operation.
|
||||
|
||||
If the submitting context is required inside the callback, it must be copied
|
||||
into the operation’s user data explicitly.
|
||||
|
||||
Example:
|
||||
nbio.timeout_poly(time.Second, new_clone(context), proc(_: ^Operation, ctx: ^runtime.Context) {
|
||||
context = ctx^
|
||||
free(ctx)
|
||||
})
|
||||
|
||||
|
||||
**Callback scheduling guarantees**:
|
||||
|
||||
Callbacks are guaranteed to be invoked in a later tick, never synchronously.
|
||||
This means that the operation returned from a procedure is at least valid till the end of the
|
||||
current tick, because an operation is freed after it's callback is called.
|
||||
Thus you can set user data after an execution is queued, or call `remove`, removing subtle "race"
|
||||
conditions and simplifying control flow.
|
||||
*/
|
||||
package nbio
|
||||
84
core/nbio/errors.odin
Normal file
84
core/nbio/errors.odin
Normal file
@@ -0,0 +1,84 @@
|
||||
package nbio
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:reflect"
|
||||
|
||||
Error :: intrinsics.type_merge(
|
||||
Network_Error,
|
||||
union #shared_nil {
|
||||
General_Error,
|
||||
FS_Error,
|
||||
},
|
||||
)
|
||||
#assert(size_of(Error) == 8)
|
||||
|
||||
// Errors regarding general usage of the event loop.
|
||||
General_Error :: enum i32 {
|
||||
None,
|
||||
|
||||
Allocation_Failed = i32(PLATFORM_ERR_ALLOCATION_FAILED),
|
||||
Unsupported = i32(PLATFORM_ERR_UNSUPPORTED),
|
||||
}
|
||||
|
||||
// Errors gotten from file system operations.
|
||||
FS_Error :: enum i32 {
|
||||
None,
|
||||
Unsupported = i32(PLATFORM_ERR_UNSUPPORTED),
|
||||
Allocation_Failed = i32(PLATFORM_ERR_ALLOCATION_FAILED),
|
||||
Timeout = i32(PLATFORM_ERR_TIMEOUT),
|
||||
Invalid_Argument = i32(PLATFORM_ERR_INVALID_ARGUMENT),
|
||||
Permission_Denied = i32(PLATFORM_ERR_PERMISSION_DENIED),
|
||||
EOF = i32(PLATFORM_ERR_EOF),
|
||||
Exists = i32(PLATFORM_ERR_EXISTS),
|
||||
Not_Found = i32(PLATFORM_ERR_NOT_FOUND),
|
||||
}
|
||||
|
||||
Platform_Error :: _Platform_Error
|
||||
|
||||
error_string :: proc(err: Error) -> string {
|
||||
err := err
|
||||
variant := any{
|
||||
id = reflect.union_variant_typeid(err),
|
||||
data = &err,
|
||||
}
|
||||
str := reflect.enum_string(variant)
|
||||
|
||||
if str == "" {
|
||||
#partial switch uerr in err {
|
||||
case FS_Error:
|
||||
str, _ = reflect.enum_name_from_value(Platform_Error(uerr))
|
||||
case General_Error:
|
||||
str, _ = reflect.enum_name_from_value(Platform_Error(uerr))
|
||||
}
|
||||
}
|
||||
if str == "" {
|
||||
str = "Unknown"
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
error_string_recv :: proc(recv_err: Recv_Error) -> string {
|
||||
switch err in recv_err {
|
||||
case TCP_Recv_Error: return error_string(err)
|
||||
case UDP_Recv_Error: return error_string(err)
|
||||
case: return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
error_string_send :: proc(send_err: Send_Error) -> string {
|
||||
switch err in send_err {
|
||||
case TCP_Send_Error: return error_string(err)
|
||||
case UDP_Send_Error: return error_string(err)
|
||||
case: return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
error_string_sendfile :: proc(send_err: Send_File_Error) -> string {
|
||||
switch err in send_err {
|
||||
case TCP_Send_Error: return error_string(err)
|
||||
case FS_Error: return error_string(err)
|
||||
case: return "Unknown"
|
||||
}
|
||||
}
|
||||
16
core/nbio/errors_linux.odin
Normal file
16
core/nbio/errors_linux.odin
Normal file
@@ -0,0 +1,16 @@
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "core:sys/linux"
|
||||
|
||||
PLATFORM_ERR_UNSUPPORTED :: linux.Errno.ENOSYS
|
||||
PLATFORM_ERR_ALLOCATION_FAILED :: linux.Errno.ENOMEM
|
||||
PLATFORM_ERR_TIMEOUT :: linux.Errno.ECANCELED
|
||||
PLATFORM_ERR_INVALID_ARGUMENT :: linux.Errno.EINVAL
|
||||
PLATFORM_ERR_OVERFLOW :: linux.Errno.E2BIG
|
||||
PLATFORM_ERR_NOT_FOUND :: linux.Errno.ENOENT
|
||||
PLATFORM_ERR_EXISTS :: linux.Errno.EEXIST
|
||||
PLATFORM_ERR_PERMISSION_DENIED :: linux.Errno.EPERM
|
||||
PLATFORM_ERR_EOF :: -100 // There is no EOF errno, we use negative for our own error codes.
|
||||
|
||||
_Platform_Error :: linux.Errno
|
||||
20
core/nbio/errors_others.odin
Normal file
20
core/nbio/errors_others.odin
Normal file
@@ -0,0 +1,20 @@
|
||||
#+build !darwin
|
||||
#+build !freebsd
|
||||
#+build !openbsd
|
||||
#+build !netbsd
|
||||
#+build !linux
|
||||
#+build !windows
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
PLATFORM_ERR_UNSUPPORTED :: 1
|
||||
PLATFORM_ERR_ALLOCATION_FAILED :: 2
|
||||
PLATFORM_ERR_TIMEOUT :: 3
|
||||
PLATFORM_ERR_INVALID_ARGUMENT :: 4
|
||||
PLATFORM_ERR_OVERFLOW :: 5
|
||||
PLATFORM_ERR_NOT_FOUND :: 6
|
||||
PLATFORM_ERR_EXISTS :: 7
|
||||
PLATFORM_ERR_PERMISSION_DENIED :: 8
|
||||
PLATFORM_ERR_EOF :: 9
|
||||
|
||||
_Platform_Error :: enum i32 {}
|
||||
17
core/nbio/errors_posix.odin
Normal file
17
core/nbio/errors_posix.odin
Normal file
@@ -0,0 +1,17 @@
|
||||
#+build darwin, freebsd, netbsd, openbsd
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "core:sys/posix"
|
||||
|
||||
PLATFORM_ERR_UNSUPPORTED :: posix.Errno.ENOSYS
|
||||
PLATFORM_ERR_ALLOCATION_FAILED :: posix.Errno.ENOMEM
|
||||
PLATFORM_ERR_TIMEOUT :: posix.Errno.ECANCELED
|
||||
PLATFORM_ERR_INVALID_ARGUMENT :: posix.Errno.EINVAL
|
||||
PLATFORM_ERR_OVERFLOW :: posix.Errno.E2BIG
|
||||
PLATFORM_ERR_NOT_FOUND :: posix.Errno.ENOENT
|
||||
PLATFORM_ERR_EXISTS :: posix.Errno.EEXIST
|
||||
PLATFORM_ERR_PERMISSION_DENIED :: posix.Errno.EPERM
|
||||
PLATFORM_ERR_EOF :: -100 // There is no EOF errno, we use negative for our own error codes.
|
||||
|
||||
_Platform_Error :: posix.Errno
|
||||
17
core/nbio/errors_windows.odin
Normal file
17
core/nbio/errors_windows.odin
Normal file
@@ -0,0 +1,17 @@
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import win "core:sys/windows"
|
||||
|
||||
PLATFORM_ERR_UNSUPPORTED :: win.System_Error.NOT_SUPPORTED
|
||||
|
||||
PLATFORM_ERR_ALLOCATION_FAILED :: win.System_Error.OUTOFMEMORY
|
||||
PLATFORM_ERR_TIMEOUT :: win.System_Error.WAIT_TIMEOUT
|
||||
PLATFORM_ERR_INVALID_ARGUMENT :: win.System_Error.BAD_ARGUMENTS
|
||||
PLATFORM_ERR_OVERFLOW :: win.System_Error.BUFFER_OVERFLOW
|
||||
PLATFORM_ERR_NOT_FOUND :: win.System_Error.FILE_NOT_FOUND
|
||||
PLATFORM_ERR_EXISTS :: win.System_Error.FILE_EXISTS
|
||||
PLATFORM_ERR_PERMISSION_DENIED :: win.System_Error.ACCESS_DENIED
|
||||
PLATFORM_ERR_EOF :: win.System_Error.HANDLE_EOF
|
||||
|
||||
_Platform_Error :: win.System_Error
|
||||
256
core/nbio/impl.odin
Normal file
256
core/nbio/impl.odin
Normal file
@@ -0,0 +1,256 @@
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "base:runtime"
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:container/pool"
|
||||
import "core:container/queue"
|
||||
import "core:net"
|
||||
import "core:strings"
|
||||
import "core:sync"
|
||||
import "core:time"
|
||||
import "core:reflect"
|
||||
|
||||
@(init, private)
|
||||
init_thread_local_cleaner :: proc "contextless" () {
|
||||
runtime.add_thread_local_cleaner(proc() {
|
||||
l := &_tls_event_loop
|
||||
if l.refs > 0 {
|
||||
l.refs = 1
|
||||
_release_thread_event_loop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@(thread_local)
|
||||
_tls_event_loop: Event_Loop
|
||||
|
||||
_acquire_thread_event_loop :: proc() -> General_Error {
|
||||
l := &_tls_event_loop
|
||||
if l.err == nil && l.refs == 0 {
|
||||
when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 && ODIN_OS != .Orca {
|
||||
allocator := runtime.default_wasm_allocator()
|
||||
} else {
|
||||
allocator := runtime.heap_allocator()
|
||||
}
|
||||
|
||||
l.queue.data.allocator = allocator
|
||||
|
||||
if pool_err := pool.init(&l.operation_pool, "_pool_link"); pool_err != nil {
|
||||
l.err = .Allocation_Failed
|
||||
return l.err
|
||||
}
|
||||
defer if l.err != nil { pool.destroy(&l.operation_pool) }
|
||||
|
||||
l.err = _init(l, allocator)
|
||||
l.now = time.now()
|
||||
}
|
||||
|
||||
if l.err != nil {
|
||||
return l.err
|
||||
}
|
||||
|
||||
l.refs += 1
|
||||
return nil
|
||||
}
|
||||
|
||||
_release_thread_event_loop :: proc() {
|
||||
l := &_tls_event_loop
|
||||
if l.err != nil {
|
||||
assert(l.refs == 0)
|
||||
return
|
||||
}
|
||||
|
||||
if l.refs > 0 {
|
||||
l.refs -= 1
|
||||
if l.refs == 0 {
|
||||
queue.destroy(&l.queue)
|
||||
pool.destroy(&l.operation_pool)
|
||||
_destroy(l)
|
||||
l^ = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_current_thread_event_loop :: #force_inline proc(loc := #caller_location) -> (^Event_Loop) {
|
||||
l := &_tls_event_loop
|
||||
|
||||
if intrinsics.expect(l.refs == 0, false) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
_tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) {
|
||||
// Receive operations queued from other threads first.
|
||||
{
|
||||
sync.guard(&l.queue_mu)
|
||||
for op in queue.pop_front_safe(&l.queue) {
|
||||
_exec(op)
|
||||
}
|
||||
}
|
||||
|
||||
return __tick(l, timeout)
|
||||
}
|
||||
|
||||
_listen_tcp :: proc(
|
||||
l: ^Event_Loop,
|
||||
endpoint: Endpoint,
|
||||
backlog := 1000,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
socket: TCP_Socket,
|
||||
err: Network_Error,
|
||||
) {
|
||||
family := family_from_endpoint(endpoint)
|
||||
socket = create_tcp_socket(family, l, loc) or_return
|
||||
defer if err != nil { close(socket, l=l) }
|
||||
|
||||
net.set_option(socket, .Reuse_Address, true)
|
||||
|
||||
bind(socket, endpoint) or_return
|
||||
|
||||
_listen(socket, backlog) or_return
|
||||
return
|
||||
}
|
||||
|
||||
_read_entire_file :: proc(l: ^Event_Loop, path: string, user_data: rawptr, cb: Read_Entire_File_Callback, allocator := context.allocator, dir := CWD) {
|
||||
open_poly3(path, user_data, cb, allocator, on_open, dir=dir, l=l)
|
||||
|
||||
on_open :: proc(op: ^Operation, user_data: rawptr, cb: Read_Entire_File_Callback, allocator: runtime.Allocator) {
|
||||
if op.open.err != nil {
|
||||
cb(user_data, nil, {.Open, op.open.err})
|
||||
return
|
||||
}
|
||||
|
||||
stat_poly3(op.open.handle, user_data, cb, allocator, on_stat)
|
||||
}
|
||||
|
||||
on_stat :: proc(op: ^Operation, user_data: rawptr, cb: Read_Entire_File_Callback, allocator: runtime.Allocator) {
|
||||
if op.stat.err != nil {
|
||||
close(op.stat.handle)
|
||||
cb(user_data, nil, {.Stat, op.stat.err})
|
||||
return
|
||||
}
|
||||
|
||||
if op.stat.type != .Regular {
|
||||
close(op.stat.handle)
|
||||
cb(user_data, nil, {.Stat, .Unsupported})
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := make([]byte, op.stat.size, allocator)
|
||||
if err != nil {
|
||||
close(op.stat.handle)
|
||||
cb(user_data, nil, {.Read, .Allocation_Failed})
|
||||
return
|
||||
}
|
||||
|
||||
read_poly3(op.stat.handle, 0, buf, user_data, cb, allocator, on_read, all=true)
|
||||
}
|
||||
|
||||
on_read :: proc(op: ^Operation, user_data: rawptr, cb: Read_Entire_File_Callback, allocator: runtime.Allocator) {
|
||||
close(op.read.handle)
|
||||
|
||||
if op.read.err != nil {
|
||||
delete(op.read.buf, allocator)
|
||||
cb(user_data, nil, {.Read, op.read.err})
|
||||
return
|
||||
}
|
||||
|
||||
assert(op.read.read == len(op.read.buf))
|
||||
cb(user_data, op.read.buf, {})
|
||||
}
|
||||
}
|
||||
|
||||
NBIO_DEBUG :: #config(NBIO_DEBUG, false)
|
||||
|
||||
Debuggable :: union {
|
||||
Operation_Type,
|
||||
string,
|
||||
int,
|
||||
time.Time,
|
||||
time.Duration,
|
||||
}
|
||||
|
||||
@(disabled=!NBIO_DEBUG)
|
||||
debug :: proc(contents: ..Debuggable, location := #caller_location) {
|
||||
if context.logger.procedure == nil || .Debug < context.logger.lowest_level {
|
||||
return
|
||||
}
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
b: strings.Builder
|
||||
b.buf.allocator = context.temp_allocator
|
||||
|
||||
strings.write_string(&b, "[nbio] ")
|
||||
|
||||
for content, i in contents {
|
||||
switch val in content {
|
||||
case Operation_Type:
|
||||
name, _ := reflect.enum_name_from_value(val)
|
||||
strings.write_string(&b, name)
|
||||
case string:
|
||||
strings.write_string(&b, val)
|
||||
case int:
|
||||
strings.write_int(&b, val)
|
||||
case time.Duration:
|
||||
ms := time.duration_milliseconds(val)
|
||||
strings.write_f64(&b, ms, 'f')
|
||||
strings.write_string(&b, "ms")
|
||||
|
||||
case time.Time:
|
||||
buf: [time.MIN_HMS_LEN+1]byte
|
||||
h, m, s, ns := time.precise_clock_from_time(val)
|
||||
buf[8] = '.'
|
||||
buf[7] = '0' + u8(s % 10); s /= 10
|
||||
buf[6] = '0' + u8(s)
|
||||
buf[5] = ':'
|
||||
buf[4] = '0' + u8(m % 10); m /= 10
|
||||
buf[3] = '0' + u8(m)
|
||||
buf[2] = ':'
|
||||
buf[1] = '0' + u8(h % 10); h /= 10
|
||||
buf[0] = '0' + u8(h)
|
||||
|
||||
strings.write_string(&b, string(buf[:]))
|
||||
strings.write_int(&b, ns)
|
||||
}
|
||||
|
||||
if i < len(contents)-1 {
|
||||
strings.write_byte(&b, ' ')
|
||||
}
|
||||
}
|
||||
|
||||
context.logger.procedure(context.logger.data, .Debug, strings.to_string(b), context.logger.options, location)
|
||||
}
|
||||
|
||||
warn :: proc(text: string, location := #caller_location) {
|
||||
if context.logger.procedure == nil || .Warning < context.logger.lowest_level {
|
||||
return
|
||||
}
|
||||
|
||||
context.logger.procedure(context.logger.data, .Warning, text, context.logger.options, location)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
constraint_bufs_to_max_rw :: proc(bufs: [][]byte) -> (constrained: [][]byte, total: int) {
|
||||
for buf in bufs {
|
||||
total += len(buf)
|
||||
}
|
||||
|
||||
constrained = bufs
|
||||
for n := total; n > MAX_RW; {
|
||||
last := &constrained[len(constrained)-1]
|
||||
take := min(len(last), n-MAX_RW)
|
||||
last^ = last[:take]
|
||||
if len(last) == 0 {
|
||||
constrained = constrained[:len(constrained)-1]
|
||||
}
|
||||
n -= take
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
1421
core/nbio/impl_linux.odin
Normal file
1421
core/nbio/impl_linux.odin
Normal file
File diff suppressed because it is too large
Load Diff
217
core/nbio/impl_others.odin
Normal file
217
core/nbio/impl_others.odin
Normal file
@@ -0,0 +1,217 @@
|
||||
#+build !darwin
|
||||
#+build !freebsd
|
||||
#+build !openbsd
|
||||
#+build !netbsd
|
||||
#+build !linux
|
||||
#+build !windows
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "core:container/avl"
|
||||
import "core:container/pool"
|
||||
import "core:container/queue"
|
||||
import "core:mem"
|
||||
import "core:slice"
|
||||
import "core:time"
|
||||
|
||||
_FULLY_SUPPORTED :: false
|
||||
|
||||
_Event_Loop :: struct {
|
||||
completed: queue.Queue(^Operation),
|
||||
timeouts: avl.Tree(^Operation),
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
_Handle :: uintptr
|
||||
|
||||
_CWD :: Handle(-100)
|
||||
|
||||
MAX_RW :: mem.Gigabyte
|
||||
|
||||
_Operation :: struct {
|
||||
removed: bool,
|
||||
}
|
||||
|
||||
_Accept :: struct {}
|
||||
|
||||
_Close :: struct {}
|
||||
|
||||
_Dial :: struct {}
|
||||
|
||||
_Recv :: struct {
|
||||
small_bufs: [1][]byte,
|
||||
}
|
||||
|
||||
_Send :: struct {
|
||||
small_bufs: [1][]byte,
|
||||
}
|
||||
|
||||
_Read :: struct {}
|
||||
|
||||
_Write :: struct {}
|
||||
|
||||
_Timeout :: struct {
|
||||
expires: time.Time,
|
||||
}
|
||||
|
||||
_Poll :: struct {}
|
||||
|
||||
_Send_File :: struct {}
|
||||
|
||||
_Open :: struct {}
|
||||
|
||||
_Stat :: struct {}
|
||||
|
||||
_Splice :: struct {}
|
||||
|
||||
_Remove :: struct {}
|
||||
|
||||
_Link_Timeout :: struct {}
|
||||
|
||||
_init :: proc(l: ^Event_Loop, allocator: mem.Allocator) -> (rerr: General_Error) {
|
||||
l.allocator = allocator
|
||||
l.completed.data.allocator = allocator
|
||||
|
||||
avl.init_cmp(&l.timeouts, timeouts_cmp, allocator)
|
||||
|
||||
return nil
|
||||
|
||||
timeouts_cmp :: #force_inline proc(a, b: ^Operation) -> slice.Ordering {
|
||||
switch {
|
||||
case a.timeout._impl.expires._nsec < b.timeout._impl.expires._nsec:
|
||||
return .Less
|
||||
case a.timeout._impl.expires._nsec > b.timeout._impl.expires._nsec:
|
||||
return .Greater
|
||||
case uintptr(a) < uintptr(b):
|
||||
return .Less
|
||||
case uintptr(a) > uintptr(b):
|
||||
return .Greater
|
||||
case:
|
||||
assert(a == b)
|
||||
return .Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_destroy :: proc(l: ^Event_Loop) {
|
||||
queue.destroy(&l.completed)
|
||||
avl.destroy(&l.timeouts, false)
|
||||
}
|
||||
|
||||
__tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> General_Error {
|
||||
l.now = time.now()
|
||||
|
||||
for op in queue.pop_front_safe(&l.completed) {
|
||||
if !op._impl.removed {
|
||||
op.cb(op)
|
||||
}
|
||||
if !op.detached {
|
||||
pool.put(&l.operation_pool, op)
|
||||
}
|
||||
}
|
||||
|
||||
iter := avl.iterator(&l.timeouts, .Forward)
|
||||
for node in avl.iterator_next(&iter) {
|
||||
op := node.value
|
||||
cexpires := time.diff(l.now, op.timeout._impl.expires)
|
||||
|
||||
done := cexpires <= 0
|
||||
if done {
|
||||
op.cb(op)
|
||||
avl.remove_node(&l.timeouts, node)
|
||||
if !op.detached {
|
||||
pool.put(&l.operation_pool, op)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_create_socket :: proc(l: ^Event_Loop, family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
|
||||
return nil, .Network_Unreachable
|
||||
}
|
||||
|
||||
_listen :: proc(socket: TCP_Socket, backlog := 1000) -> Listen_Error {
|
||||
return .Network_Unreachable
|
||||
}
|
||||
|
||||
_exec :: proc(op: ^Operation) {
|
||||
switch op.type {
|
||||
case .Timeout:
|
||||
_, _, err := avl.find_or_insert(&op.l.timeouts, op)
|
||||
if err != nil {
|
||||
panic("nbio: allocation failure")
|
||||
}
|
||||
return
|
||||
case .Accept:
|
||||
op.accept.err = .Network_Unreachable
|
||||
case .Close:
|
||||
op.close.err = .Unsupported
|
||||
case .Dial:
|
||||
op.dial.err = Dial_Error.Network_Unreachable
|
||||
case .Recv:
|
||||
switch _ in op.recv.socket {
|
||||
case TCP_Socket: op.recv.err = TCP_Recv_Error.Network_Unreachable
|
||||
case UDP_Socket: op.recv.err = UDP_Recv_Error.Network_Unreachable
|
||||
case: op.recv.err = TCP_Recv_Error.Network_Unreachable
|
||||
}
|
||||
case .Send:
|
||||
switch _ in op.send.socket {
|
||||
case TCP_Socket: op.send.err = TCP_Send_Error.Network_Unreachable
|
||||
case UDP_Socket: op.send.err = UDP_Send_Error.Network_Unreachable
|
||||
case: op.send.err = TCP_Send_Error.Network_Unreachable
|
||||
}
|
||||
case .Send_File:
|
||||
op.sendfile.err = .Network_Unreachable
|
||||
case .Read:
|
||||
op.read.err = .Unsupported
|
||||
case .Write:
|
||||
op.write.err = .Unsupported
|
||||
case .Poll:
|
||||
op.poll.result = .Error
|
||||
case .Open:
|
||||
op.open.err = .Unsupported
|
||||
case .Stat:
|
||||
op.stat.err = .Unsupported
|
||||
case .None, ._Link_Timeout, ._Remove, ._Splice:
|
||||
fallthrough
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
_, err := queue.push_back(&op.l.completed, op)
|
||||
if err != nil {
|
||||
panic("nbio: allocation failure")
|
||||
}
|
||||
}
|
||||
|
||||
_remove :: proc(target: ^Operation) {
|
||||
#partial switch target.type {
|
||||
case .Timeout:
|
||||
avl.remove_value(&target.l.timeouts, target)
|
||||
if !target.detached {
|
||||
pool.put(&target.l.operation_pool, target)
|
||||
}
|
||||
case:
|
||||
target._impl.removed = true
|
||||
}
|
||||
}
|
||||
|
||||
_open_sync :: proc(l: ^Event_Loop, path: string, dir: Handle, mode: File_Flags, perm: Permissions) -> (handle: Handle, err: FS_Error) {
|
||||
return 0, FS_Error.Unsupported
|
||||
}
|
||||
|
||||
_associate_handle :: proc(handle: uintptr, l: ^Event_Loop) -> (Handle, Association_Error) {
|
||||
return Handle(handle), nil
|
||||
}
|
||||
|
||||
_associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop) -> Association_Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_wake_up :: proc(l: ^Event_Loop) {
|
||||
}
|
||||
1403
core/nbio/impl_posix.odin
Normal file
1403
core/nbio/impl_posix.odin
Normal file
File diff suppressed because it is too large
Load Diff
29
core/nbio/impl_posix_darwin.odin
Normal file
29
core/nbio/impl_posix_darwin.odin
Normal file
@@ -0,0 +1,29 @@
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "core:net"
|
||||
import "core:sys/posix"
|
||||
|
||||
foreign import lib "system:System"
|
||||
|
||||
posix_sendfile :: proc(fd: Handle, s: TCP_Socket, offset, nbytes: int) -> (sent: int, ok := true) {
|
||||
foreign lib {
|
||||
@(link_name="sendfile")
|
||||
_posix_sendfile :: proc (fd, s: posix.FD, offset: posix.off_t, len: ^posix.off_t, hdtr: rawptr, flags: i32) -> posix.result ---
|
||||
}
|
||||
|
||||
len := posix.off_t(nbytes)
|
||||
if _posix_sendfile(posix.FD(fd), posix.FD(s), posix.off_t(offset), &len, nil, 0) != .OK {
|
||||
ok = false
|
||||
}
|
||||
sent = int(len)
|
||||
return
|
||||
}
|
||||
|
||||
posix_listen_error :: net._listen_error
|
||||
posix_accept_error :: net._accept_error
|
||||
posix_dial_error :: net._dial_error
|
||||
posix_tcp_send_error :: net._tcp_send_error
|
||||
posix_udp_send_error :: net._udp_send_error
|
||||
posix_tcp_recv_error :: net._tcp_recv_error
|
||||
posix_udp_recv_error :: net._udp_recv_error
|
||||
52
core/nbio/impl_posix_freebsd.odin
Normal file
52
core/nbio/impl_posix_freebsd.odin
Normal file
@@ -0,0 +1,52 @@
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "core:net"
|
||||
import "core:sys/posix"
|
||||
import "core:sys/freebsd"
|
||||
|
||||
foreign import lib "system:c"
|
||||
|
||||
// TODO: rewrite freebsd implementation to use `sys/freebsd` instead of `sys/posix`.
|
||||
|
||||
posix_sendfile :: proc(fd: Handle, s: TCP_Socket, offset, nbytes: int) -> (sent: int, ok := true) {
|
||||
foreign lib {
|
||||
@(link_name="sendfile")
|
||||
_posix_sendfile :: proc (fd, s: posix.FD, offset: posix.off_t, nbytes: uint, hdtr: rawptr, sbytes: ^posix.off_t, flags: i32) -> posix.result ---
|
||||
}
|
||||
|
||||
len: posix.off_t
|
||||
if _posix_sendfile(posix.FD(fd), posix.FD(s), posix.off_t(offset), uint(nbytes), nil, &len, 0) != .OK {
|
||||
ok = false
|
||||
}
|
||||
sent = int(len)
|
||||
return
|
||||
}
|
||||
|
||||
posix_listen_error :: proc() -> Listen_Error {
|
||||
return net._listen_error(freebsd.Errno(posix.errno()))
|
||||
}
|
||||
|
||||
posix_accept_error :: proc() -> Accept_Error {
|
||||
return net._accept_error(freebsd.Errno(posix.errno()))
|
||||
}
|
||||
|
||||
posix_dial_error :: proc() -> Dial_Error {
|
||||
return net._dial_error(freebsd.Errno(posix.errno()))
|
||||
}
|
||||
|
||||
posix_tcp_send_error :: proc() -> TCP_Send_Error {
|
||||
return net._tcp_send_error(freebsd.Errno(posix.errno()))
|
||||
}
|
||||
|
||||
posix_udp_send_error :: proc() -> UDP_Send_Error {
|
||||
return net._udp_send_error(freebsd.Errno(posix.errno()))
|
||||
}
|
||||
|
||||
posix_tcp_recv_error :: proc() -> TCP_Recv_Error {
|
||||
return net._tcp_recv_error(freebsd.Errno(posix.errno()))
|
||||
}
|
||||
|
||||
posix_udp_recv_error :: proc() -> UDP_Recv_Error {
|
||||
return net._udp_recv_error(freebsd.Errno(posix.errno()))
|
||||
}
|
||||
12
core/nbio/impl_posix_netbsd.odin
Normal file
12
core/nbio/impl_posix_netbsd.odin
Normal file
@@ -0,0 +1,12 @@
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "core:net"
|
||||
|
||||
posix_listen_error :: net._listen_error
|
||||
posix_accept_error :: net._accept_error
|
||||
posix_dial_error :: net._dial_error
|
||||
posix_tcp_send_error :: net._tcp_send_error
|
||||
posix_udp_send_error :: net._udp_send_error
|
||||
posix_tcp_recv_error :: net._tcp_recv_error
|
||||
posix_udp_recv_error :: net._udp_recv_error
|
||||
12
core/nbio/impl_posix_openbsd.odin
Normal file
12
core/nbio/impl_posix_openbsd.odin
Normal file
@@ -0,0 +1,12 @@
|
||||
#+private
|
||||
package nbio
|
||||
|
||||
import "core:net"
|
||||
|
||||
posix_listen_error :: net._listen_error
|
||||
posix_accept_error :: net._accept_error
|
||||
posix_dial_error :: net._dial_error
|
||||
posix_tcp_send_error :: net._tcp_send_error
|
||||
posix_udp_send_error :: net._udp_send_error
|
||||
posix_tcp_recv_error :: net._tcp_recv_error
|
||||
posix_udp_recv_error :: net._udp_recv_error
|
||||
1733
core/nbio/impl_windows.odin
Normal file
1733
core/nbio/impl_windows.odin
Normal file
File diff suppressed because it is too large
Load Diff
436
core/nbio/nbio.odin
Normal file
436
core/nbio/nbio.odin
Normal file
@@ -0,0 +1,436 @@
|
||||
package nbio
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:container/pool"
|
||||
import "core:container/queue"
|
||||
import "core:net"
|
||||
import "core:sync"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
If the package is fully supported on the current target. If it is not it will compile but work
|
||||
in a matter where things are unimplemented.
|
||||
|
||||
Additionally if it is `FULLY_SUPPORTED` it may still return `.Unsupported` in `acquire_thread_event_loop`
|
||||
If the target does not support the needed syscalls for operating the package.
|
||||
*/
|
||||
FULLY_SUPPORTED :: _FULLY_SUPPORTED
|
||||
|
||||
/*
|
||||
An event loop, one per thread, consider the fields private.
|
||||
Do not copy.
|
||||
*/
|
||||
Event_Loop :: struct /* #no_copy */ {
|
||||
using impl: _Event_Loop,
|
||||
err: General_Error,
|
||||
refs: int,
|
||||
now: time.Time,
|
||||
|
||||
// Queue that is used to queue operations from another thread to be executed on this thread.
|
||||
// TODO: Better data-structure.
|
||||
queue: queue.Queue(^Operation),
|
||||
queue_mu: sync.Mutex,
|
||||
|
||||
operation_pool: pool.Pool(Operation),
|
||||
}
|
||||
|
||||
Handle :: _Handle
|
||||
|
||||
// The maximum size of user arguments for an operation, can be increased at the cost of more RAM.
|
||||
MAX_USER_ARGUMENTS :: #config(NBIO_MAX_USER_ARGUMENTS, 4)
|
||||
#assert(MAX_USER_ARGUMENTS >= 4)
|
||||
|
||||
Operation :: struct {
|
||||
cb: Callback,
|
||||
user_data: [MAX_USER_ARGUMENTS + 1]rawptr,
|
||||
detached: bool,
|
||||
type: Operation_Type,
|
||||
using specifics: Specifics,
|
||||
|
||||
_impl: _Operation `fmt:"-"`,
|
||||
using _: struct #raw_union {
|
||||
_pool_link: ^Operation,
|
||||
l: ^Event_Loop,
|
||||
},
|
||||
}
|
||||
|
||||
Specifics :: struct #raw_union {
|
||||
accept: Accept `raw_union_tag:"type=.Accept"`,
|
||||
close: Close `raw_union_tag:"type=.Close"`,
|
||||
dial: Dial `raw_union_tag:"type=.Dial"`,
|
||||
read: Read `raw_union_tag:"type=.Read"`,
|
||||
recv: Recv `raw_union_tag:"type=.Recv"`,
|
||||
send: Send `raw_union_tag:"type=.Send"`,
|
||||
write: Write `raw_union_tag:"type=.Write"`,
|
||||
timeout: Timeout `raw_union_tag:"type=.Timeout"`,
|
||||
poll: Poll `raw_union_tag:"type=.Poll"`,
|
||||
sendfile: Send_File `raw_union_tag:"type=.Send_File"`,
|
||||
open: Open `raw_union_tag:"type=.Open"`,
|
||||
stat: Stat `raw_union_tag:"type=.Stat"`,
|
||||
|
||||
_remove: _Remove `raw_union_tag:"type=._Remove"`,
|
||||
_link_timeout: _Link_Timeout `raw_union_tag:"type=._Link_Timeout"`,
|
||||
_splice: _Splice `raw_union_tag:"type=._Splice"`,
|
||||
}
|
||||
|
||||
Operation_Type :: enum i32 {
|
||||
None,
|
||||
Accept,
|
||||
Close,
|
||||
Dial,
|
||||
Read,
|
||||
Recv,
|
||||
Send,
|
||||
Write,
|
||||
Timeout,
|
||||
Poll,
|
||||
Send_File,
|
||||
Open,
|
||||
Stat,
|
||||
|
||||
_Link_Timeout,
|
||||
_Remove,
|
||||
_Splice,
|
||||
}
|
||||
|
||||
Callback :: #type proc(op: ^Operation)
|
||||
|
||||
/*
|
||||
Initialize or increment the reference counted event loop for the current thread.
|
||||
*/
|
||||
acquire_thread_event_loop :: proc() -> General_Error {
|
||||
return _acquire_thread_event_loop()
|
||||
}
|
||||
|
||||
/*
|
||||
Destroy or decrease the reference counted event loop for the current thread.
|
||||
*/
|
||||
release_thread_event_loop :: proc() {
|
||||
_release_thread_event_loop()
|
||||
}
|
||||
|
||||
current_thread_event_loop :: proc(loc := #caller_location) -> ^Event_Loop {
|
||||
return _current_thread_event_loop(loc)
|
||||
}
|
||||
|
||||
/*
|
||||
Each time you call this the implementation checks its state
|
||||
and calls any callbacks which are ready. You would typically call this in a loop.
|
||||
|
||||
Blocks for up-to timeout waiting for events if there is nothing to do.
|
||||
*/
|
||||
tick :: proc(timeout: time.Duration = NO_TIMEOUT) -> General_Error {
|
||||
l := &_tls_event_loop
|
||||
if l.refs == 0 { return nil }
|
||||
return _tick(l, timeout)
|
||||
}
|
||||
|
||||
/*
|
||||
Runs the event loop by ticking in a loop until there is no more work to be done.
|
||||
*/
|
||||
run :: proc() -> General_Error {
|
||||
l := &_tls_event_loop
|
||||
if l.refs == 0 { return nil }
|
||||
|
||||
acquire_thread_event_loop()
|
||||
defer release_thread_event_loop()
|
||||
|
||||
for num_waiting() > 0 {
|
||||
if errno := _tick(l, NO_TIMEOUT); errno != nil {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Runs the event loop by ticking in a loop until there is no more work to be done, or the flag `done` is `true`.
|
||||
*/
|
||||
run_until :: proc(done: ^bool) -> General_Error {
|
||||
l := &_tls_event_loop
|
||||
if l.refs == 0 { return nil }
|
||||
|
||||
acquire_thread_event_loop()
|
||||
defer release_thread_event_loop()
|
||||
|
||||
for num_waiting() > 0 && !intrinsics.volatile_load(done) {
|
||||
if errno := _tick(l, NO_TIMEOUT); errno != nil {
|
||||
return errno
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the number of in-progress operations to be completed on the event loop.
|
||||
*/
|
||||
num_waiting :: proc(l: Maybe(^Event_Loop) = nil) -> int {
|
||||
l_ := l.? or_else &_tls_event_loop
|
||||
if l_.refs == 0 { return 0 }
|
||||
return pool.num_outstanding(&l_.operation_pool)
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the current time (cached at most at the beginning of the current tick).
|
||||
*/
|
||||
now :: proc() -> time.Time {
|
||||
if _tls_event_loop.now == {} {
|
||||
return time.now()
|
||||
}
|
||||
return _tls_event_loop.now
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the given operation from the event loop. The callback of it won't be called and resources are freed.
|
||||
|
||||
Calling `remove`:
|
||||
- Cancels the operation if it has not yet completed
|
||||
- Prevents the callback from being called
|
||||
|
||||
Cancellation via `remove` is *final* and silent:
|
||||
- The callback will never be invoked
|
||||
- No error is delivered
|
||||
- The operation must be considered dead after removal
|
||||
|
||||
WARN: the operation could have already been (partially or completely) completed.
|
||||
A send with `all` set to true could have sent a portion already.
|
||||
But also, a send that could be completed without blocking could have been completed.
|
||||
You just won't get a callback.
|
||||
|
||||
WARN: once an operation's callback is called it can not be removed anymore (use after free).
|
||||
|
||||
WARN: needs to be called from the thread of the event loop the target belongs to.
|
||||
|
||||
Common use would be to cancel a timeout, remove a polling, or remove an `accept` before calling `close` on it's socket.
|
||||
*/
|
||||
remove :: proc(target: ^Operation) {
|
||||
if target == nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert(target.type != .None)
|
||||
|
||||
if target.l != &_tls_event_loop {
|
||||
panic("nbio.remove called on different thread")
|
||||
}
|
||||
|
||||
_remove(target)
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a socket for use in `nbio` and relates it to the given event loop.
|
||||
|
||||
Inputs:
|
||||
- family: Should this be an IP4 or IP6 socket
|
||||
- protocol: The type of socket (TCP or UDP)
|
||||
- l: The event loop to associate it with, defaults to the current thread's loop
|
||||
|
||||
Returns:
|
||||
- socket: The created socket, consider `create_{udp|tcp}_socket` for a typed socket instead of the union
|
||||
- err: A network error (`Create_Socket_Error`, or `Set_Blocking_Error`) which happened while opening
|
||||
*/
|
||||
create_socket :: proc(
|
||||
family: Address_Family,
|
||||
protocol: Socket_Protocol,
|
||||
l: ^Event_Loop = nil,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
socket: Any_Socket,
|
||||
err: Create_Socket_Error,
|
||||
) {
|
||||
return _create_socket(l if l != nil else _current_thread_event_loop(loc), family, protocol)
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a UDP socket for use in `nbio` and relates it to the given event loop.
|
||||
|
||||
Inputs:
|
||||
- family: Should this be an IP4 or IP6 socket
|
||||
- l: The event loop to associate it with, defaults to the current thread's loop
|
||||
|
||||
Returns:
|
||||
- socket: The created UDP socket
|
||||
- err: A network error (`Create_Socket_Error`, or `Set_Blocking_Error`) which happened while opening
|
||||
*/
|
||||
create_udp_socket :: proc(family: Address_Family, l: ^Event_Loop = nil, loc := #caller_location) -> (net.UDP_Socket, Create_Socket_Error) {
|
||||
socket, err := create_socket(family, .UDP, l, loc)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return socket.(UDP_Socket), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a TCP socket for use in `nbio` and relates it to the given event loop.
|
||||
|
||||
Inputs:
|
||||
- family: Should this be an IP4 or IP6 socket
|
||||
- l: The event loop to associate it with, defaults to the current thread's loop
|
||||
|
||||
Returns:
|
||||
- socket: The created TCP socket
|
||||
- err: A network error (`Create_Socket_Error`, or `Set_Blocking_Error`) which happened while opening
|
||||
*/
|
||||
create_tcp_socket :: proc(family: Address_Family, l: ^Event_Loop = nil, loc := #caller_location) -> (net.TCP_Socket, Create_Socket_Error) {
|
||||
socket, err := create_socket(family, .TCP, l, loc)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return socket.(TCP_Socket), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a socket, sets non blocking mode, relates it to the given IO, binds the socket to the given endpoint and starts listening.
|
||||
|
||||
Inputs:
|
||||
- endpoint: Where to bind the socket to
|
||||
- backlog: The maximum length to which the queue of pending connections may grow, before refusing connections
|
||||
- l: The event loop to associate the socket with, defaults to the current thread's loop
|
||||
|
||||
Returns:
|
||||
- socket: The opened, bound and listening socket
|
||||
- err: A network error (`Create_Socket_Error`, `Bind_Error`, or `Listen_Error`) that has happened
|
||||
*/
|
||||
listen_tcp :: proc(endpoint: Endpoint, backlog := 1000, l: ^Event_Loop = nil, loc := #caller_location) -> (socket: TCP_Socket, err: net.Network_Error) {
|
||||
assert(backlog > 0 && backlog < int(max(i32)))
|
||||
return _listen_tcp(l if l != nil else _current_thread_event_loop(loc), endpoint, backlog)
|
||||
}
|
||||
|
||||
/*
|
||||
Opens a file and associates it with the event loop.
|
||||
|
||||
Inputs:
|
||||
- path: path to the file, if not absolute: relative from `dir`
|
||||
- dir: directory that `path` is relative from (if it is relative), defaults to the current working directory
|
||||
- mode: open mode, defaults to read-only
|
||||
- perm: permissions to use when creating a file, defaults to read+write for everybody
|
||||
- l: event loop to associate the file with, defaults to the current thread's
|
||||
|
||||
Returns:
|
||||
- handle: The file handle
|
||||
- err: An error if it occurred
|
||||
*/
|
||||
open_sync :: proc(path: string, dir: Handle = CWD, mode: File_Flags = {.Read}, perm := Permissions_Default_File, l: ^Event_Loop = nil, loc := #caller_location) -> (handle: Handle, err: FS_Error) {
|
||||
return _open_sync(l if l != nil else _current_thread_event_loop(loc), path, dir, mode, perm)
|
||||
}
|
||||
|
||||
Association_Error :: enum {
|
||||
None,
|
||||
// The given file/handle/socket was not opened in a mode that it can be made non-blocking afterwards.
|
||||
//
|
||||
// On Windows, this can happen when a file is not opened with the `FILE_FLAG_OVERLAPPED` flag.
|
||||
// If using `core:os`, that is set when you specify the `O_NONBLOCK` flag.
|
||||
// There is no way to add that after the fact.
|
||||
Not_Possible_To_Associate,
|
||||
// The given handle is not a valid handle.
|
||||
Invalid_Handle,
|
||||
// No network connection, or the network stack is not initialized.
|
||||
Network_Unreachable,
|
||||
}
|
||||
|
||||
/*
|
||||
Associate the given OS handle, not opened through this package, with the event loop.
|
||||
|
||||
Consider using this package's `open` or `open_sync` directly instead.
|
||||
|
||||
The handle returned is for convenience, it is actually still the same handle as given.
|
||||
Thus you should not close the given handle.
|
||||
|
||||
On Windows, this can error when a file is not opened with the `FILE_FLAG_OVERLAPPED` flag.
|
||||
If using `core:os`, that is set when you specify the `O_NONBLOCK` flag.
|
||||
There is no way to add that after the fact.
|
||||
*/
|
||||
associate_handle :: proc(handle: uintptr, l: ^Event_Loop = nil, loc := #caller_location) -> (Handle, Association_Error) {
|
||||
return _associate_handle(handle, l if l != nil else _current_thread_event_loop(loc))
|
||||
}
|
||||
|
||||
/*
|
||||
Associate the given socket, not created through this package, with the event loop.
|
||||
|
||||
Consider using this package's `create_socket` directly instead.
|
||||
*/
|
||||
associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop = nil, loc := #caller_location) -> Association_Error {
|
||||
return _associate_socket(socket, l if l != nil else _current_thread_event_loop(loc))
|
||||
}
|
||||
|
||||
Read_Entire_File_Error :: struct {
|
||||
operation: Operation_Type,
|
||||
value: FS_Error,
|
||||
}
|
||||
|
||||
Read_Entire_File_Callback :: #type proc(user_data: rawptr, data: []byte, err: Read_Entire_File_Error)
|
||||
|
||||
/*
|
||||
Combines multiple operations (open, stat, read, close) into one that reads an entire regular file.
|
||||
|
||||
The error contains the `operation` that the error happened on.
|
||||
|
||||
Inputs:
|
||||
- path: path to the file, if not absolute: relative from `dir`
|
||||
- user_data: a pointer passed through into the callback
|
||||
- cb: the callback to call once completed, called with the user data, file data, and an optional error
|
||||
- allocator: the allocator to allocate the file's contents onto
|
||||
- dir: directory that `path` is relative from (if it is relative), defaults to the current working directory
|
||||
- l: event loop to execute the operation on
|
||||
*/
|
||||
read_entire_file :: proc(path: string, user_data: rawptr, cb: Read_Entire_File_Callback, allocator := context.allocator, dir := CWD, l: ^Event_Loop = nil, loc := #caller_location) {
|
||||
_read_entire_file(l if l != nil else _current_thread_event_loop(loc), path, user_data, cb, allocator, dir)
|
||||
}
|
||||
|
||||
/*
|
||||
Detach an operation from the package's lifetime management.
|
||||
|
||||
By default the operation's lifetime is managed by the package and freed after a callback is called.
|
||||
Calling this function detaches the operation from this lifetime.
|
||||
You are expected to call `reattach` to give the package back this operation.
|
||||
*/
|
||||
detach :: proc(op: ^Operation) {
|
||||
op.detached = true
|
||||
}
|
||||
|
||||
/*
|
||||
Reattach an operation to the package's lifetime management.
|
||||
*/
|
||||
reattach :: proc(op: ^Operation) {
|
||||
pool.put(&op.l.operation_pool, op)
|
||||
}
|
||||
|
||||
/*
|
||||
Execute an operation.
|
||||
|
||||
If the operation is attached to another thread's event loop, it is queued to be executed on that event loop,
|
||||
optionally waking that loop up (from a blocking `tick`) with `trigger_wake_up`.
|
||||
*/
|
||||
exec :: proc(op: ^Operation, trigger_wake_up := true) {
|
||||
if op.l == &_tls_event_loop {
|
||||
_exec(op)
|
||||
} else {
|
||||
{
|
||||
// TODO: Better data-structure.
|
||||
sync.guard(&op.l.queue_mu)
|
||||
_, err := queue.push_back(&op.l.queue, op)
|
||||
if err != nil {
|
||||
panic("exec: queueing operation failed due to memory allocation failure")
|
||||
}
|
||||
}
|
||||
if trigger_wake_up {
|
||||
wake_up(op.l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Wake up an event loop on another thread which may be blocking for completed operations.
|
||||
|
||||
Commonly used with `exec` from a worker thread to have the event loop pick up that work.
|
||||
Note that by default `exec` already calls this procedure.
|
||||
*/
|
||||
wake_up :: proc(l: ^Event_Loop) {
|
||||
if l == &_tls_event_loop {
|
||||
return
|
||||
}
|
||||
_wake_up(l)
|
||||
}
|
||||
39
core/nbio/net.odin
Normal file
39
core/nbio/net.odin
Normal file
@@ -0,0 +1,39 @@
|
||||
package nbio
|
||||
|
||||
import "core:net"
|
||||
|
||||
Network_Error :: net.Network_Error
|
||||
Accept_Error :: net.Accept_Error
|
||||
Dial_Error :: net.Dial_Error
|
||||
Send_Error :: net.Send_Error
|
||||
TCP_Send_Error :: net.TCP_Send_Error
|
||||
UDP_Send_Error :: net.UDP_Send_Error
|
||||
Recv_Error :: net.Recv_Error
|
||||
TCP_Recv_Error :: net.TCP_Recv_Error
|
||||
UDP_Recv_Error :: net.UDP_Recv_Error
|
||||
Listen_Error :: net.Listen_Error
|
||||
Create_Socket_Error :: net.Create_Socket_Error
|
||||
|
||||
Address_Family :: net.Address_Family
|
||||
Socket_Protocol :: net.Socket_Protocol
|
||||
|
||||
Address :: net.Address
|
||||
IP4_Address :: net.IP4_Address
|
||||
IP6_Address :: net.IP6_Address
|
||||
|
||||
Endpoint :: net.Endpoint
|
||||
|
||||
TCP_Socket :: net.TCP_Socket
|
||||
UDP_Socket :: net.UDP_Socket
|
||||
Any_Socket :: net.Any_Socket
|
||||
|
||||
IP4_Any :: net.IP4_Any
|
||||
IP6_Any :: net.IP6_Any
|
||||
IP4_Loopback :: net.IP4_Loopback
|
||||
IP6_Loopback :: net.IP6_Loopback
|
||||
|
||||
family_from_endpoint :: net.family_from_endpoint
|
||||
bind :: net.bind
|
||||
bound_endpoint :: net.bound_endpoint
|
||||
parse_endpoint :: net.parse_endpoint
|
||||
endpoint_to_string :: net.endpoint_to_string
|
||||
2473
core/nbio/ops.odin
Normal file
2473
core/nbio/ops.odin
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
#+build windows, linux, darwin, freebsd
|
||||
package net
|
||||
|
||||
/*
|
||||
@@ -22,7 +21,6 @@ package net
|
||||
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:fmt"
|
||||
|
||||
/*
|
||||
Expects an IPv4 address with no leading or trailing whitespace:
|
||||
@@ -473,13 +471,20 @@ join_port :: proc(address_or_host: string, port: int, allocator := context.alloc
|
||||
addr := parse_address(addr_or_host)
|
||||
if addr == nil {
|
||||
// hostname
|
||||
fmt.sbprintf(&b, "%v:%v", addr_or_host, port)
|
||||
strings.write_string(&b, addr_or_host)
|
||||
strings.write_string(&b, ":")
|
||||
strings.write_int(&b, port)
|
||||
} else {
|
||||
switch _ in addr {
|
||||
case IP4_Address:
|
||||
fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port)
|
||||
strings.write_string(&b, address_to_string(addr))
|
||||
strings.write_string(&b, ":")
|
||||
strings.write_int(&b, port)
|
||||
case IP6_Address:
|
||||
fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port)
|
||||
strings.write_string(&b, "[")
|
||||
strings.write_string(&b, address_to_string(addr))
|
||||
strings.write_string(&b, "]:")
|
||||
strings.write_int(&b, port)
|
||||
}
|
||||
}
|
||||
return strings.to_string(b)
|
||||
@@ -509,7 +514,13 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) ->
|
||||
b := strings.builder_make(allocator)
|
||||
switch v in addr {
|
||||
case IP4_Address:
|
||||
fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3])
|
||||
strings.write_uint(&b, uint(v[0]))
|
||||
strings.write_byte(&b, '.')
|
||||
strings.write_uint(&b, uint(v[1]))
|
||||
strings.write_byte(&b, '.')
|
||||
strings.write_uint(&b, uint(v[2]))
|
||||
strings.write_byte(&b, '.')
|
||||
strings.write_uint(&b, uint(v[3]))
|
||||
case IP6_Address:
|
||||
// First find the longest run of zeroes.
|
||||
Zero_Run :: struct {
|
||||
@@ -563,25 +574,33 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) ->
|
||||
for val, i in v {
|
||||
if best.start == i || best.end == i {
|
||||
// For the left and right side of the best zero run, print a `:`.
|
||||
fmt.sbprint(&b, ":")
|
||||
strings.write_string(&b, ":")
|
||||
} else if i < best.start {
|
||||
/*
|
||||
If we haven't made it to the best run yet, print the digit.
|
||||
Make sure we only print a `:` after the digit if it's not
|
||||
immediately followed by the run's own leftmost `:`.
|
||||
*/
|
||||
fmt.sbprintf(&b, "%x", val)
|
||||
|
||||
buf: [32]byte
|
||||
str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {})
|
||||
strings.write_string(&b, str)
|
||||
|
||||
if i < best.start - 1 {
|
||||
fmt.sbprintf(&b, ":")
|
||||
strings.write_string(&b, ":")
|
||||
}
|
||||
} else if i > best.end {
|
||||
/*
|
||||
If there are any digits after the zero run, print them.
|
||||
But don't print the `:` at the end of the IP number.
|
||||
*/
|
||||
fmt.sbprintf(&b, "%x", val)
|
||||
|
||||
buf: [32]byte
|
||||
str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {})
|
||||
strings.write_string(&b, str)
|
||||
|
||||
if i != 7 {
|
||||
fmt.sbprintf(&b, ":")
|
||||
strings.write_string(&b, ":")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,8 +617,14 @@ endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) ->
|
||||
s := address_to_string(ep.address, context.temp_allocator)
|
||||
b := strings.builder_make(allocator)
|
||||
switch a in ep.address {
|
||||
case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port)
|
||||
case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port)
|
||||
case IP4_Address:
|
||||
strings.write_string(&b, s)
|
||||
strings.write_int(&b, ep.port)
|
||||
case IP6_Address:
|
||||
strings.write_string(&b, "[")
|
||||
strings.write_string(&b, s)
|
||||
strings.write_string(&b, "]:")
|
||||
strings.write_int(&b, ep.port)
|
||||
}
|
||||
return strings.to_string(b)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#+build windows, linux, darwin, freebsd
|
||||
package net
|
||||
|
||||
/*
|
||||
@@ -91,6 +90,7 @@ Parse_Endpoint_Error :: enum u32 {
|
||||
Resolve_Error :: enum u32 {
|
||||
None = 0,
|
||||
Unable_To_Resolve = 1,
|
||||
Allocation_Failure,
|
||||
}
|
||||
|
||||
DNS_Error :: enum u32 {
|
||||
@@ -144,11 +144,11 @@ Address :: union {IP4_Address, IP6_Address}
|
||||
IP4_Loopback :: IP4_Address{127, 0, 0, 1}
|
||||
IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}
|
||||
|
||||
IP4_Any := IP4_Address{}
|
||||
IP6_Any := IP6_Address{}
|
||||
IP4_Any :: IP4_Address{}
|
||||
IP6_Any :: IP6_Address{}
|
||||
|
||||
IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353}
|
||||
IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353}
|
||||
IP4_mDNS_Broadcast :: Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353}
|
||||
IP6_mDNS_Broadcast :: Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353}
|
||||
|
||||
Endpoint :: struct {
|
||||
address: Address,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#+build windows, linux, darwin, freebsd
|
||||
package net
|
||||
|
||||
/*
|
||||
@@ -22,13 +21,18 @@ package net
|
||||
Haesbaert: Security fixes
|
||||
*/
|
||||
|
||||
@(require) import "base:runtime"
|
||||
@(require)
|
||||
import "base:runtime"
|
||||
|
||||
import "core:bufio"
|
||||
import "core:io"
|
||||
import "core:math/rand"
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
import "core:os"
|
||||
import "core:math/rand"
|
||||
@(require) import "core:sync"
|
||||
|
||||
@(require)
|
||||
import "core:sync"
|
||||
|
||||
dns_config_initialized: sync.Once
|
||||
when ODIN_OS == .Windows {
|
||||
@@ -42,20 +46,12 @@ when ODIN_OS == .Windows {
|
||||
hosts_file = "/etc/hosts",
|
||||
}
|
||||
} else {
|
||||
#panic("Please add a configuration for this OS.")
|
||||
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{}
|
||||
}
|
||||
|
||||
/*
|
||||
Replaces environment placeholders in `dns_configuration`. Only necessary on Windows.
|
||||
Is automatically called, once, by `get_dns_records_*`.
|
||||
*/
|
||||
@(private)
|
||||
init_dns_configuration :: proc() {
|
||||
when ODIN_OS == .Windows {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
|
||||
copy(dns_configuration.hosts_file_buf[:], val)
|
||||
dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
|
||||
_init_dns_configuration()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,9 +174,7 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
|
||||
See `destroy_records`.
|
||||
*/
|
||||
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
when ODIN_OS == .Windows {
|
||||
sync.once_do(&dns_config_initialized, init_dns_configuration)
|
||||
}
|
||||
init_dns_configuration()
|
||||
return _get_dns_records_os(hostname, type, allocator)
|
||||
}
|
||||
|
||||
@@ -196,51 +190,14 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
|
||||
See `destroy_records`.
|
||||
*/
|
||||
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
when ODIN_OS == .Windows {
|
||||
sync.once_do(&dns_config_initialized, init_dns_configuration)
|
||||
}
|
||||
init_dns_configuration()
|
||||
context.allocator = allocator
|
||||
|
||||
if type != .SRV {
|
||||
// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
|
||||
ok := validate_hostname(hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
}
|
||||
id := u16be(rand.uint32())
|
||||
dns_packet_buf: [DNS_PACKET_MIN_LEN]byte = ---
|
||||
dns_packet := make_dns_packet(dns_packet_buf[:], id, hostname, type) or_return
|
||||
|
||||
hdr := DNS_Header{
|
||||
id = u16be(rand.uint32()),
|
||||
is_response = false,
|
||||
opcode = 0,
|
||||
is_authoritative = false,
|
||||
is_truncated = false,
|
||||
is_recursion_desired = true,
|
||||
is_recursion_available = false,
|
||||
response_code = DNS_Response_Code.No_Error,
|
||||
}
|
||||
|
||||
id, bits := pack_dns_header(hdr)
|
||||
dns_hdr := [6]u16be{}
|
||||
dns_hdr[0] = id
|
||||
dns_hdr[1] = bits
|
||||
dns_hdr[2] = 1
|
||||
|
||||
dns_query := [2]u16be{ u16be(type), 1 }
|
||||
|
||||
output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{}
|
||||
b := strings.builder_from_slice(output[:])
|
||||
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:]))
|
||||
ok := encode_hostname(&b, hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:]))
|
||||
|
||||
dns_packet := output[:strings.builder_len(b)]
|
||||
|
||||
dns_response_buf := [4096]u8{}
|
||||
dns_response_buf: [4096]u8 = ---
|
||||
dns_response: []u8
|
||||
for name_server in name_servers {
|
||||
conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server))
|
||||
@@ -283,6 +240,42 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type
|
||||
return
|
||||
}
|
||||
|
||||
DNS_PACKET_MIN_LEN :: (size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)
|
||||
|
||||
make_dns_packet :: proc(buf: []byte, id: u16be, hostname: string, type: DNS_Record_Type) -> (packet: []byte, err: DNS_Error) {
|
||||
assert(len(buf) >= DNS_PACKET_MIN_LEN)
|
||||
|
||||
hdr := DNS_Header{
|
||||
id = id,
|
||||
is_response = false,
|
||||
opcode = 0,
|
||||
is_authoritative = false,
|
||||
is_truncated = false,
|
||||
is_recursion_desired = true,
|
||||
is_recursion_available = false,
|
||||
response_code = DNS_Response_Code.No_Error,
|
||||
}
|
||||
|
||||
_, bits := pack_dns_header(hdr)
|
||||
dns_hdr := [6]u16be{}
|
||||
dns_hdr[0] = id
|
||||
dns_hdr[1] = bits
|
||||
dns_hdr[2] = 1
|
||||
|
||||
dns_query := [2]u16be{ u16be(type), 1 }
|
||||
|
||||
b := strings.builder_from_slice(buf[:])
|
||||
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:]))
|
||||
ok := encode_hostname(&b, hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:]))
|
||||
|
||||
return buf[:strings.builder_len(b)], nil
|
||||
}
|
||||
|
||||
// `records` slice is also destroyed.
|
||||
destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {
|
||||
context.allocator = allocator
|
||||
@@ -364,13 +357,8 @@ unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) {
|
||||
return hdr
|
||||
}
|
||||
|
||||
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
res := os.read_entire_file_from_filename(resolv_conf_path) or_return
|
||||
defer delete(res)
|
||||
resolv_str := string(res)
|
||||
|
||||
parse_resolv_conf :: proc(resolv_str: string, allocator := context.allocator) -> (name_servers: []Endpoint) {
|
||||
resolv_str := resolv_str
|
||||
id_str := "nameserver"
|
||||
id_len := len(id_str)
|
||||
|
||||
@@ -401,41 +389,51 @@ load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocato
|
||||
append(&_name_servers, endpoint)
|
||||
}
|
||||
|
||||
return _name_servers[:], true
|
||||
return _name_servers[:]
|
||||
}
|
||||
|
||||
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
|
||||
context.allocator = allocator
|
||||
parse_hosts :: proc(stream: io.Stream, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
|
||||
s := bufio.scanner_init(&{}, stream, allocator)
|
||||
defer bufio.scanner_destroy(s)
|
||||
|
||||
res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return
|
||||
defer delete(res)
|
||||
resize(&s.buf, 256)
|
||||
|
||||
_hosts := make([dynamic]DNS_Host_Entry, 0, allocator)
|
||||
hosts_str := string(res)
|
||||
for line in strings.split_lines_iterator(&hosts_str) {
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
_hosts: [dynamic]DNS_Host_Entry
|
||||
_hosts.allocator = allocator
|
||||
defer if !ok {
|
||||
for host in _hosts {
|
||||
delete(host.name, allocator)
|
||||
}
|
||||
delete(_hosts)
|
||||
}
|
||||
|
||||
splits := strings.fields(line)
|
||||
defer delete(splits)
|
||||
for bufio.scanner_scan(s) {
|
||||
line := bufio.scanner_text(s)
|
||||
|
||||
(len(splits) >= 2) or_continue
|
||||
line, _, _ = strings.partition(line, "#")
|
||||
(len(line) > 0) or_continue
|
||||
|
||||
ip_str := strings.fields_iterator(&line) or_continue
|
||||
|
||||
ip_str := splits[0]
|
||||
addr := parse_address(ip_str)
|
||||
if addr == nil {
|
||||
continue
|
||||
}
|
||||
(addr != nil) or_continue
|
||||
|
||||
for hostname in splits[1:] {
|
||||
if len(hostname) != 0 {
|
||||
append(&_hosts, DNS_Host_Entry{hostname, addr})
|
||||
}
|
||||
for hostname in strings.fields_iterator(&line) {
|
||||
(len(hostname) > 0) or_continue
|
||||
|
||||
clone, alloc_err := strings.clone(hostname, allocator)
|
||||
if alloc_err != nil { return }
|
||||
|
||||
_, alloc_err = append(&_hosts, DNS_Host_Entry{clone, addr})
|
||||
if alloc_err != nil { return }
|
||||
}
|
||||
}
|
||||
|
||||
return _hosts[:], true
|
||||
if bufio.scanner_error(s) != nil { return }
|
||||
|
||||
hosts = _hosts[:]
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// www.google.com -> 3www6google3com0
|
||||
@@ -594,7 +592,7 @@ decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.alloc
|
||||
|
||||
// Uses RFC 952 & RFC 1123
|
||||
validate_hostname :: proc(hostname: string) -> (ok: bool) {
|
||||
if len(hostname) > 255 || len(hostname) == 0 {
|
||||
if len(hostname) > NAME_MAX || len(hostname) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -604,7 +602,7 @@ validate_hostname :: proc(hostname: string) -> (ok: bool) {
|
||||
|
||||
_hostname := hostname
|
||||
for label in strings.split_iterator(&_hostname, ".") {
|
||||
if len(label) > 63 || len(label) == 0 {
|
||||
if len(label) > LABEL_MAX || len(label) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -868,4 +866,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
|
||||
xid = hdr.id
|
||||
|
||||
return _records[:], xid, true
|
||||
}
|
||||
}
|
||||
|
||||
24
core/net/dns_os.odin
Normal file
24
core/net/dns_os.odin
Normal file
@@ -0,0 +1,24 @@
|
||||
#+build darwin, freebsd, openbsd, netbsd, linux, windows, wasi
|
||||
#+private
|
||||
package net
|
||||
|
||||
import "core:os"
|
||||
|
||||
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
res := os.read_entire_file_from_filename(resolv_conf_path) or_return
|
||||
defer delete(res)
|
||||
resolv_str := string(res)
|
||||
|
||||
return parse_resolv_conf(resolv_str), true
|
||||
}
|
||||
|
||||
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
|
||||
hosts_file, err := os.open(hosts_file_path)
|
||||
if err != nil { return }
|
||||
defer os.close(hosts_file)
|
||||
|
||||
return parse_hosts(os.stream_from_handle(hosts_file), allocator)
|
||||
}
|
||||
|
||||
12
core/net/dns_others.odin
Normal file
12
core/net/dns_others.odin
Normal file
@@ -0,0 +1,12 @@
|
||||
#+build !windows
|
||||
#+build !linux
|
||||
#+build !darwin
|
||||
#+build !freebsd
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
package net
|
||||
|
||||
@(private)
|
||||
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#+build linux, darwin, freebsd
|
||||
#+build linux, darwin, freebsd, openbsd, netbsd
|
||||
package net
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
@@ -42,14 +42,19 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
|
||||
}
|
||||
|
||||
hosts, hosts_ok := load_hosts(dns_configuration.hosts_file)
|
||||
defer delete(hosts)
|
||||
if !hosts_ok {
|
||||
return nil, .Invalid_Hosts_Config_Error
|
||||
}
|
||||
defer {
|
||||
for h in hosts {
|
||||
delete(h.name)
|
||||
}
|
||||
delete(hosts)
|
||||
}
|
||||
|
||||
host_overrides := make([dynamic]DNS_Record)
|
||||
for host in hosts {
|
||||
if strings.compare(host.name, hostname) != 0 {
|
||||
if host.name != hostname {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -79,4 +84,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
|
||||
}
|
||||
|
||||
return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,29 @@ package net
|
||||
Feoramund: FreeBSD platform code
|
||||
*/
|
||||
|
||||
import "core:strings"
|
||||
import "base:runtime"
|
||||
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import "core:sync"
|
||||
|
||||
import win "core:sys/windows"
|
||||
|
||||
/*
|
||||
Replaces environment placeholders in `dns_configuration`. Only necessary on Windows.
|
||||
Is automatically called, once, by `get_dns_records_*`.
|
||||
*/
|
||||
@(private)
|
||||
_init_dns_configuration :: proc() {
|
||||
sync.once_do(&dns_config_initialized, proc() {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
|
||||
copy(dns_configuration.hosts_file_buf[:], val)
|
||||
dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
|
||||
})
|
||||
}
|
||||
|
||||
@(private)
|
||||
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
context.allocator = allocator
|
||||
@@ -171,4 +189,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
|
||||
|
||||
records = recs[:]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,11 @@ Accept_Error :: enum i32 {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
Recv_Error :: union #shared_nil {
|
||||
TCP_Recv_Error,
|
||||
UDP_Recv_Error,
|
||||
}
|
||||
|
||||
TCP_Recv_Error :: enum i32 {
|
||||
None,
|
||||
// No network connection, or the network stack is not initialized.
|
||||
@@ -187,6 +192,11 @@ UDP_Recv_Error :: enum i32 {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
Send_Error :: union #shared_nil {
|
||||
TCP_Send_Error,
|
||||
UDP_Send_Error,
|
||||
}
|
||||
|
||||
TCP_Send_Error :: enum i32 {
|
||||
None,
|
||||
// No network connection, or the network stack is not initialized.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#+build !linux
|
||||
#+build !freebsd
|
||||
#+build !windows
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
package net
|
||||
|
||||
@(private="file", thread_local)
|
||||
@@ -18,10 +20,3 @@ _last_platform_error_string :: proc() -> string {
|
||||
_set_last_platform_error :: proc(err: i32) {
|
||||
_last_error = err
|
||||
}
|
||||
|
||||
Parse_Endpoint_Error :: enum u32 {
|
||||
None = 0,
|
||||
Bad_Port = 1,
|
||||
Bad_Address,
|
||||
Bad_Hostname,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#+build darwin
|
||||
#+build darwin, netbsd, openbsd
|
||||
package net
|
||||
|
||||
/*
|
||||
@@ -63,7 +63,7 @@ _dial_error :: proc() -> Dial_Error {
|
||||
return .Already_Connecting
|
||||
case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL:
|
||||
return .Invalid_Argument
|
||||
case .WSAECONNREFUSED:
|
||||
case .WSAECONNREFUSED, .CONNECTION_REFUSED:
|
||||
return .Refused
|
||||
case .WSAEISCONN:
|
||||
return .Already_Connected
|
||||
@@ -122,7 +122,7 @@ _accept_error :: proc() -> Accept_Error {
|
||||
return .Aborted
|
||||
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK:
|
||||
return .Invalid_Argument
|
||||
case .WSAEINTR:
|
||||
case .WSAEINTR, .OPERATION_ABORTED:
|
||||
return .Interrupted
|
||||
case .WSAEINVAL:
|
||||
return .Not_Listening
|
||||
|
||||
11
core/net/interface_others.odin
Normal file
11
core/net/interface_others.odin
Normal file
@@ -0,0 +1,11 @@
|
||||
#+build !darwin
|
||||
#+build !linux
|
||||
#+build !freebsd
|
||||
#+build !windows
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
package net
|
||||
|
||||
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#+build darwin
|
||||
#+build darwin, openbsd, netbsd
|
||||
package net
|
||||
|
||||
/*
|
||||
@@ -117,32 +117,47 @@ IF_Flag :: enum u32 {
|
||||
BROADCAST,
|
||||
DEBUG,
|
||||
LOOPBACK,
|
||||
POINTTOPOINT,
|
||||
NOTRAILERS,
|
||||
RUNNING,
|
||||
NOARP,
|
||||
PROMISC,
|
||||
ALLMULTI,
|
||||
OACTIVE,
|
||||
SIMPLEX,
|
||||
LINK0,
|
||||
LINK1,
|
||||
LINK2,
|
||||
MULTICAST,
|
||||
// NOTE: different order on other BSDs but we don't even need these.
|
||||
// POINTTOPOINT,
|
||||
// NOTRAILERS,
|
||||
// RUNNING,
|
||||
// NOARP,
|
||||
// PROMISC,
|
||||
// ALLMULTI,
|
||||
// OACTIVE,
|
||||
// SIMPLEX,
|
||||
// LINK0,
|
||||
// LINK1,
|
||||
// LINK2,
|
||||
// MULTICAST,
|
||||
}
|
||||
|
||||
@(private)
|
||||
IF_Flags :: bit_set[IF_Flag; u32]
|
||||
|
||||
@(private)
|
||||
ifaddrs :: struct {
|
||||
next: ^ifaddrs,
|
||||
name: cstring,
|
||||
flags: IF_Flags,
|
||||
addr: ^posix.sockaddr,
|
||||
netmask: ^posix.sockaddr,
|
||||
dstaddr: ^posix.sockaddr,
|
||||
data: rawptr,
|
||||
when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
|
||||
@(private)
|
||||
ifaddrs :: struct {
|
||||
next: ^ifaddrs,
|
||||
name: cstring,
|
||||
flags: IF_Flags,
|
||||
addr: ^posix.sockaddr,
|
||||
netmask: ^posix.sockaddr,
|
||||
dstaddr: ^posix.sockaddr,
|
||||
data: rawptr,
|
||||
}
|
||||
} else when ODIN_OS == .NetBSD {
|
||||
@(private)
|
||||
ifaddrs :: struct {
|
||||
next: ^ifaddrs,
|
||||
name: cstring,
|
||||
flags: IF_Flags,
|
||||
addr: ^posix.sockaddr,
|
||||
netmask: ^posix.sockaddr,
|
||||
dstaddr: ^posix.sockaddr,
|
||||
data: rawptr,
|
||||
addrflags: u32,
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
@@ -1,4 +1,3 @@
|
||||
#+build windows, linux, darwin, freebsd
|
||||
package net
|
||||
|
||||
/*
|
||||
@@ -20,6 +19,35 @@ package net
|
||||
Feoramund: FreeBSD platform code
|
||||
*/
|
||||
|
||||
Socket_Option :: enum i32 {
|
||||
Broadcast = i32(_SOCKET_OPTION_BROADCAST),
|
||||
Reuse_Address = i32(_SOCKET_OPTION_REUSE_ADDRESS),
|
||||
Keep_Alive = i32(_SOCKET_OPTION_KEEP_ALIVE),
|
||||
Out_Of_Bounds_Data_Inline = i32(_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE),
|
||||
Linger = i32(_SOCKET_OPTION_LINGER),
|
||||
Receive_Buffer_Size = i32(_SOCKET_OPTION_RECEIVE_BUFFER_SIZE),
|
||||
Send_Buffer_Size = i32(_SOCKET_OPTION_SEND_BUFFER_SIZE),
|
||||
Receive_Timeout = i32(_SOCKET_OPTION_RECEIVE_TIMEOUT),
|
||||
Send_Timeout = i32(_SOCKET_OPTION_SEND_TIMEOUT),
|
||||
|
||||
TCP_Nodelay = i32(_SOCKET_OPTION_TCP_NODELAY),
|
||||
|
||||
Use_Loopback = i32(_SOCKET_OPTION_USE_LOOPBACK),
|
||||
Reuse_Port = i32(_SOCKET_OPTION_REUSE_PORT),
|
||||
No_SIGPIPE_From_EPIPE = i32(_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE),
|
||||
Reuse_Port_Load_Balancing = i32(_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING),
|
||||
|
||||
Exclusive_Addr_Use = i32(_SOCKET_OPTION_EXCLUSIVE_ADDR_USE),
|
||||
Conditional_Accept = i32(_SOCKET_OPTION_CONDITIONAL_ACCEPT),
|
||||
Dont_Linger = i32(_SOCKET_OPTION_DONT_LINGER),
|
||||
}
|
||||
|
||||
Shutdown_Manner :: enum i32 {
|
||||
Receive = i32(_SHUTDOWN_MANNER_RECEIVE),
|
||||
Send = i32(_SHUTDOWN_MANNER_SEND),
|
||||
Both = i32(_SHUTDOWN_MANNER_BOTH),
|
||||
}
|
||||
|
||||
any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket {
|
||||
switch s in socket {
|
||||
case TCP_Socket: return Socket(s)
|
||||
|
||||
@@ -20,45 +20,35 @@ package net
|
||||
Feoramund: FreeBSD platform code
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:sys/freebsd"
|
||||
import "core:time"
|
||||
|
||||
Fd :: freebsd.Fd
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
// TODO: Test and implement more socket options.
|
||||
// DEBUG
|
||||
Reuse_Address = cast(c.int)freebsd.Socket_Option.REUSEADDR,
|
||||
Keep_Alive = cast(c.int)freebsd.Socket_Option.KEEPALIVE,
|
||||
// DONTROUTE
|
||||
Broadcast = cast(c.int)freebsd.Socket_Option.BROADCAST,
|
||||
Use_Loopback = cast(c.int)freebsd.Socket_Option.USELOOPBACK,
|
||||
Linger = cast(c.int)freebsd.Socket_Option.LINGER,
|
||||
Out_Of_Bounds_Data_Inline = cast(c.int)freebsd.Socket_Option.OOBINLINE,
|
||||
Reuse_Port = cast(c.int)freebsd.Socket_Option.REUSEPORT,
|
||||
// TIMESTAMP
|
||||
No_SIGPIPE_From_EPIPE = cast(c.int)freebsd.Socket_Option.NOSIGPIPE,
|
||||
// ACCEPTFILTER
|
||||
// BINTIME
|
||||
// NO_OFFLOAD
|
||||
// NO_DDP
|
||||
Reuse_Port_Load_Balancing = cast(c.int)freebsd.Socket_Option.REUSEPORT_LB,
|
||||
// RERROR
|
||||
_SOCKET_OPTION_BROADCAST :: freebsd.Socket_Option.BROADCAST
|
||||
_SOCKET_OPTION_REUSE_ADDRESS :: freebsd.Socket_Option.REUSEADDR
|
||||
_SOCKET_OPTION_KEEP_ALIVE :: freebsd.Socket_Option.KEEPALIVE
|
||||
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: freebsd.Socket_Option.OOBINLINE
|
||||
_SOCKET_OPTION_LINGER :: freebsd.Socket_Option.LINGER
|
||||
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: freebsd.Socket_Option.RCVBUF
|
||||
_SOCKET_OPTION_SEND_BUFFER_SIZE :: freebsd.Socket_Option.SNDBUF
|
||||
_SOCKET_OPTION_RECEIVE_TIMEOUT :: freebsd.Socket_Option.RCVTIMEO
|
||||
_SOCKET_OPTION_SEND_TIMEOUT :: freebsd.Socket_Option.SNDTIMEO
|
||||
|
||||
Send_Buffer_Size = cast(c.int)freebsd.Socket_Option.SNDBUF,
|
||||
Receive_Buffer_Size = cast(c.int)freebsd.Socket_Option.RCVBUF,
|
||||
// SNDLOWAT
|
||||
// RCVLOWAT
|
||||
Send_Timeout = cast(c.int)freebsd.Socket_Option.SNDTIMEO,
|
||||
Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO,
|
||||
}
|
||||
_SOCKET_OPTION_TCP_NODELAY :: -1
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = cast(c.int)freebsd.Shutdown_Method.RD,
|
||||
Send = cast(c.int)freebsd.Shutdown_Method.WR,
|
||||
Both = cast(c.int)freebsd.Shutdown_Method.RDWR,
|
||||
}
|
||||
_SOCKET_OPTION_USE_LOOPBACK :: freebsd.Socket_Option.USELOOPBACK
|
||||
_SOCKET_OPTION_REUSE_PORT :: freebsd.Socket_Option.REUSEPORT
|
||||
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: freebsd.Socket_Option.NOSIGPIPE
|
||||
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: freebsd.Socket_Option.REUSEPORT_LB
|
||||
|
||||
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
|
||||
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
|
||||
_SOCKET_OPTION_DONT_LINGER :: -1
|
||||
|
||||
_SHUTDOWN_MANNER_RECEIVE :: freebsd.Shutdown_Method.RD
|
||||
_SHUTDOWN_MANNER_SEND :: freebsd.Shutdown_Method.WR
|
||||
_SHUTDOWN_MANNER_BOTH :: freebsd.Shutdown_Method.RDWR
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
|
||||
@@ -272,7 +262,7 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
|
||||
ptr: rawptr
|
||||
len: freebsd.socklen_t
|
||||
|
||||
switch option {
|
||||
#partial switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Keep_Alive,
|
||||
@@ -344,7 +334,7 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
case:
|
||||
unimplemented("set_option() option not yet implemented", loc)
|
||||
return .Invalid_Option
|
||||
}
|
||||
|
||||
real_socket := any_socket_to_socket(socket)
|
||||
|
||||
@@ -21,28 +21,33 @@ package net
|
||||
Feoramund: FreeBSD platform code
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:time"
|
||||
import "core:sys/linux"
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
Reuse_Address = c.int(linux.Socket_Option.REUSEADDR),
|
||||
Keep_Alive = c.int(linux.Socket_Option.KEEPALIVE),
|
||||
Out_Of_Bounds_Data_Inline = c.int(linux.Socket_Option.OOBINLINE),
|
||||
TCP_Nodelay = c.int(linux.Socket_TCP_Option.NODELAY),
|
||||
Linger = c.int(linux.Socket_Option.LINGER),
|
||||
Receive_Buffer_Size = c.int(linux.Socket_Option.RCVBUF),
|
||||
Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF),
|
||||
Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO),
|
||||
Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO),
|
||||
Broadcast = c.int(linux.Socket_Option.BROADCAST),
|
||||
}
|
||||
_SOCKET_OPTION_BROADCAST :: linux.Socket_Option.BROADCAST
|
||||
_SOCKET_OPTION_REUSE_ADDRESS :: linux.Socket_Option.REUSEADDR
|
||||
_SOCKET_OPTION_KEEP_ALIVE :: linux.Socket_Option.KEEPALIVE
|
||||
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: linux.Socket_Option.OOBINLINE
|
||||
_SOCKET_OPTION_LINGER :: linux.Socket_Option.LINGER
|
||||
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: linux.Socket_Option.RCVBUF
|
||||
_SOCKET_OPTION_SEND_BUFFER_SIZE :: linux.Socket_Option.SNDBUF
|
||||
_SOCKET_OPTION_RECEIVE_TIMEOUT :: linux.Socket_Option.RCVTIMEO
|
||||
_SOCKET_OPTION_SEND_TIMEOUT :: linux.Socket_Option.SNDTIMEO
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = c.int(linux.Shutdown_How.RD),
|
||||
Send = c.int(linux.Shutdown_How.WR),
|
||||
Both = c.int(linux.Shutdown_How.RDWR),
|
||||
}
|
||||
_SOCKET_OPTION_TCP_NODELAY :: linux.Socket_TCP_Option.NODELAY
|
||||
|
||||
_SOCKET_OPTION_USE_LOOPBACK :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT :: -1
|
||||
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
|
||||
|
||||
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
|
||||
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
|
||||
_SOCKET_OPTION_DONT_LINGER :: -1
|
||||
|
||||
_SHUTDOWN_MANNER_RECEIVE :: linux.Shutdown_How.RD
|
||||
_SHUTDOWN_MANNER_SEND :: linux.Shutdown_How.WR
|
||||
_SHUTDOWN_MANNER_BOTH :: linux.Shutdown_How.RDWR
|
||||
|
||||
// Wrappers and unwrappers for system-native types
|
||||
|
||||
@@ -347,7 +352,7 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
|
||||
int_value: i32
|
||||
timeval_value: linux.Time_Val
|
||||
errno: linux.Errno
|
||||
switch option {
|
||||
#partial switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Keep_Alive,
|
||||
@@ -400,10 +405,14 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
|
||||
panic("set_option() value must be an integer here", loc)
|
||||
}
|
||||
errno = linux.setsockopt(os_sock, level, int(option), &int_value)
|
||||
case:
|
||||
return .Invalid_Socket
|
||||
}
|
||||
|
||||
if errno != .NONE {
|
||||
return _socket_option_error(errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
105
core/net/socket_others.odin
Normal file
105
core/net/socket_others.odin
Normal file
@@ -0,0 +1,105 @@
|
||||
#+build !darwin
|
||||
#+build !linux
|
||||
#+build !freebsd
|
||||
#+build !windows
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
#+private
|
||||
package net
|
||||
|
||||
_SOCKET_OPTION_BROADCAST :: -1
|
||||
_SOCKET_OPTION_REUSE_ADDRESS :: -1
|
||||
_SOCKET_OPTION_KEEP_ALIVE :: -1
|
||||
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: -1
|
||||
_SOCKET_OPTION_LINGER :: -1
|
||||
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: -1
|
||||
_SOCKET_OPTION_SEND_BUFFER_SIZE :: -1
|
||||
_SOCKET_OPTION_RECEIVE_TIMEOUT :: -1
|
||||
_SOCKET_OPTION_SEND_TIMEOUT :: -1
|
||||
|
||||
_SOCKET_OPTION_TCP_NODELAY :: -1
|
||||
|
||||
_SOCKET_OPTION_USE_LOOPBACK :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT :: -1
|
||||
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
|
||||
|
||||
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
|
||||
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
|
||||
_SOCKET_OPTION_DONT_LINGER :: -1
|
||||
|
||||
_SHUTDOWN_MANNER_RECEIVE :: -1
|
||||
_SHUTDOWN_MANNER_SEND :: -1
|
||||
_SHUTDOWN_MANNER_BOTH :: -1
|
||||
|
||||
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (sock: TCP_Socket, err: Network_Error) {
|
||||
err = Create_Socket_Error.Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (sock: Any_Socket, err: Create_Socket_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
err = Create_Socket_Error.Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_close :: proc(skt: Any_Socket) {
|
||||
}
|
||||
|
||||
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
|
||||
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
|
||||
return .Network_Unreachable
|
||||
}
|
||||
|
||||
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
|
||||
err = .Network_Unreachable
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#+build darwin
|
||||
#+build darwin, netbsd, openbsd
|
||||
package net
|
||||
|
||||
/*
|
||||
@@ -20,28 +20,33 @@ package net
|
||||
Feoramund: FreeBSD platform code
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:sys/posix"
|
||||
import "core:time"
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
Broadcast = c.int(posix.Sock_Option.BROADCAST),
|
||||
Reuse_Address = c.int(posix.Sock_Option.REUSEADDR),
|
||||
Keep_Alive = c.int(posix.Sock_Option.KEEPALIVE),
|
||||
Out_Of_Bounds_Data_Inline = c.int(posix.Sock_Option.OOBINLINE),
|
||||
TCP_Nodelay = c.int(posix.TCP_NODELAY),
|
||||
Linger = c.int(posix.Sock_Option.LINGER),
|
||||
Receive_Buffer_Size = c.int(posix.Sock_Option.RCVBUF),
|
||||
Send_Buffer_Size = c.int(posix.Sock_Option.SNDBUF),
|
||||
Receive_Timeout = c.int(posix.Sock_Option.RCVTIMEO),
|
||||
Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO),
|
||||
}
|
||||
_SOCKET_OPTION_BROADCAST :: posix.Sock_Option.BROADCAST
|
||||
_SOCKET_OPTION_REUSE_ADDRESS :: posix.Sock_Option.REUSEADDR
|
||||
_SOCKET_OPTION_KEEP_ALIVE :: posix.Sock_Option.KEEPALIVE
|
||||
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: posix.Sock_Option.OOBINLINE
|
||||
_SOCKET_OPTION_LINGER :: posix.Sock_Option.LINGER
|
||||
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: posix.Sock_Option.RCVBUF
|
||||
_SOCKET_OPTION_SEND_BUFFER_SIZE :: posix.Sock_Option.SNDBUF
|
||||
_SOCKET_OPTION_RECEIVE_TIMEOUT :: posix.Sock_Option.RCVTIMEO
|
||||
_SOCKET_OPTION_SEND_TIMEOUT :: posix.Sock_Option.SNDTIMEO
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = c.int(posix.SHUT_RD),
|
||||
Send = c.int(posix.SHUT_WR),
|
||||
Both = c.int(posix.SHUT_RDWR),
|
||||
}
|
||||
_SOCKET_OPTION_TCP_NODELAY :: posix.TCP_NODELAY
|
||||
|
||||
_SOCKET_OPTION_USE_LOOPBACK :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT :: -1
|
||||
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
|
||||
|
||||
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
|
||||
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
|
||||
_SOCKET_OPTION_DONT_LINGER :: -1
|
||||
|
||||
_SHUTDOWN_MANNER_RECEIVE :: posix.SHUT_RD
|
||||
_SHUTDOWN_MANNER_SEND :: posix.SHUT_WR
|
||||
_SHUTDOWN_MANNER_BOTH :: posix.SHUT_RDWR
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
|
||||
@@ -273,7 +278,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
|
||||
ptr: rawptr
|
||||
len: posix.socklen_t
|
||||
|
||||
switch option {
|
||||
#partial switch option {
|
||||
case
|
||||
.Broadcast,
|
||||
.Reuse_Address,
|
||||
@@ -327,6 +332,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
case:
|
||||
return .Invalid_Option
|
||||
}
|
||||
|
||||
skt := any_socket_to_socket(s)
|
||||
@@ -24,59 +24,30 @@ import "core:c"
|
||||
import win "core:sys/windows"
|
||||
import "core:time"
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
// bool: Whether the address that this socket is bound to can be reused by other sockets.
|
||||
// This allows you to bypass the cooldown period if a program dies while the socket is bound.
|
||||
Reuse_Address = win.SO_REUSEADDR,
|
||||
_SOCKET_OPTION_BROADCAST :: win.SO_BROADCAST
|
||||
_SOCKET_OPTION_REUSE_ADDRESS :: win.SO_REUSEADDR
|
||||
_SOCKET_OPTION_KEEP_ALIVE :: win.SO_KEEPALIVE
|
||||
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: win.SO_OOBINLINE
|
||||
_SOCKET_OPTION_LINGER :: win.SO_LINGER
|
||||
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: win.SO_RCVBUF
|
||||
_SOCKET_OPTION_SEND_BUFFER_SIZE :: win.SO_SNDBUF
|
||||
_SOCKET_OPTION_RECEIVE_TIMEOUT :: win.SO_RCVTIMEO
|
||||
_SOCKET_OPTION_SEND_TIMEOUT :: win.SO_SNDTIMEO
|
||||
|
||||
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
|
||||
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
|
||||
_SOCKET_OPTION_TCP_NODELAY :: win.TCP_NODELAY
|
||||
|
||||
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
|
||||
Keep_Alive = win.SO_KEEPALIVE,
|
||||
_SOCKET_OPTION_USE_LOOPBACK :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT :: -1
|
||||
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
|
||||
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
|
||||
|
||||
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
|
||||
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
|
||||
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: win.SO_EXCLUSIVEADDRUSE
|
||||
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: win.SO_CONDITIONAL_ACCEPT
|
||||
_SOCKET_OPTION_DONT_LINGER :: win.SO_DONTLINGER
|
||||
|
||||
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
|
||||
Dont_Linger = win.SO_DONTLINGER,
|
||||
|
||||
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
|
||||
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
|
||||
|
||||
// bool: When true, disables send-coalescing, therefore reducing latency.
|
||||
TCP_Nodelay = win.TCP_NODELAY,
|
||||
|
||||
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
|
||||
// is some remaining data waiting to be sent, and net.close() is called.
|
||||
Linger = win.SO_LINGER,
|
||||
|
||||
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
|
||||
Receive_Buffer_Size = win.SO_RCVBUF,
|
||||
|
||||
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
|
||||
Send_Buffer_Size = win.SO_SNDBUF,
|
||||
|
||||
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
|
||||
// For non-blocking sockets, ignored.
|
||||
// Use a value of zero to potentially wait forever.
|
||||
Receive_Timeout = win.SO_RCVTIMEO,
|
||||
|
||||
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
|
||||
// For non-blocking sockets, ignored.
|
||||
// Use a value of zero to potentially wait forever.
|
||||
Send_Timeout = win.SO_SNDTIMEO,
|
||||
|
||||
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
|
||||
Broadcast = win.SO_BROADCAST,
|
||||
}
|
||||
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = win.SD_RECEIVE,
|
||||
Send = win.SD_SEND,
|
||||
Both = win.SD_BOTH,
|
||||
}
|
||||
_SHUTDOWN_MANNER_RECEIVE :: win.SD_RECEIVE
|
||||
_SHUTDOWN_MANNER_SEND :: win.SD_SEND
|
||||
_SHUTDOWN_MANNER_BOTH :: win.SD_BOTH
|
||||
|
||||
@(init, private)
|
||||
ensure_winsock_initialized :: proc "contextless" () {
|
||||
@@ -322,7 +293,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
|
||||
ptr: rawptr
|
||||
len: c.int
|
||||
|
||||
switch option {
|
||||
#partial switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Exclusive_Addr_Use,
|
||||
@@ -383,6 +354,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
case:
|
||||
return .Invalid_Option
|
||||
}
|
||||
|
||||
socket := any_socket_to_socket(s)
|
||||
|
||||
@@ -67,7 +67,7 @@ File_Flag :: enum {
|
||||
Trunc,
|
||||
Sparse,
|
||||
Inheritable,
|
||||
|
||||
Non_Blocking,
|
||||
Unbuffered_IO,
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ _open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File,
|
||||
if .Excl in flags { sys_flags += {.EXCL} }
|
||||
if .Sync in flags { sys_flags += {.DSYNC} }
|
||||
if .Trunc in flags { sys_flags += {.TRUNC} }
|
||||
if .Non_Blocking in flags { sys_flags += {.NONBLOCK} }
|
||||
if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
|
||||
|
||||
fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)transmute(u32)perm)
|
||||
|
||||
@@ -61,12 +61,13 @@ _open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File,
|
||||
}
|
||||
}
|
||||
|
||||
if .Append in flags { sys_flags += {.APPEND} }
|
||||
if .Create in flags { sys_flags += {.CREAT} }
|
||||
if .Excl in flags { sys_flags += {.EXCL} }
|
||||
if .Sync in flags { sys_flags += {.DSYNC} }
|
||||
if .Trunc in flags { sys_flags += {.TRUNC} }
|
||||
if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
|
||||
if .Append in flags { sys_flags += {.APPEND} }
|
||||
if .Create in flags { sys_flags += {.CREAT} }
|
||||
if .Excl in flags { sys_flags += {.EXCL} }
|
||||
if .Sync in flags { sys_flags += {.DSYNC} }
|
||||
if .Trunc in flags { sys_flags += {.TRUNC} }
|
||||
if .Non_Blocking in flags { sys_flags += {.NONBLOCK} }
|
||||
if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
@@ -185,8 +185,9 @@ _open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File,
|
||||
if .Trunc in flags { oflags += {.TRUNC} }
|
||||
|
||||
fdflags: wasi.fdflags_t
|
||||
if .Append in flags { fdflags += {.APPEND} }
|
||||
if .Sync in flags { fdflags += {.SYNC} }
|
||||
if .Append in flags { fdflags += {.APPEND} }
|
||||
if .Sync in flags { fdflags += {.SYNC} }
|
||||
if .Non_Blocking in flags { fdflags += {.NONBLOCK} }
|
||||
|
||||
// NOTE: rights are adjusted to what this package's functions might want to call.
|
||||
rights: wasi.rights_t
|
||||
|
||||
@@ -126,7 +126,11 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: Permissions) -> (h
|
||||
// NOTE(bill): Open has just asked to create a file in read-only mode.
|
||||
// If the file already exists, to make it akin to a *nix open call,
|
||||
// the call preserves the existing permissions.
|
||||
h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil)
|
||||
nix_attrs := win32.FILE_ATTRIBUTE_NORMAL
|
||||
if .Non_Blocking in flags {
|
||||
nix_attrs |= win32.FILE_FLAG_OVERLAPPED
|
||||
}
|
||||
h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, nix_attrs, nil)
|
||||
if h == win32.INVALID_HANDLE {
|
||||
switch e := win32.GetLastError(); e {
|
||||
case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND:
|
||||
@@ -140,6 +144,10 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: Permissions) -> (h
|
||||
}
|
||||
}
|
||||
|
||||
if .Non_Blocking in flags {
|
||||
attrs |= win32.FILE_FLAG_OVERLAPPED
|
||||
}
|
||||
|
||||
h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil)
|
||||
if h == win32.INVALID_HANDLE {
|
||||
return 0, _get_platform_error()
|
||||
|
||||
@@ -14,7 +14,7 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
|
||||
//
|
||||
// The caller must `close` the file once finished with.
|
||||
@(require_results)
|
||||
create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
|
||||
create_temp_file :: proc(dir, pattern: string, additional_flags: File_Flags = {}) -> (f: ^File, err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
dir := dir if dir != "" else temp_directory(temp_allocator) or_return
|
||||
prefix, suffix := _prefix_and_suffix(pattern) or_return
|
||||
@@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
|
||||
attempts := 0
|
||||
for {
|
||||
name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix)
|
||||
f, err = open(name, {.Read, .Write, .Create, .Excl}, Permissions_Read_Write_All)
|
||||
f, err = open(name, {.Read, .Write, .Create, .Excl} + additional_flags, Permissions_Read_Write_All)
|
||||
if err == .Exist {
|
||||
close(f)
|
||||
attempts += 1
|
||||
|
||||
@@ -333,8 +333,14 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Erro
|
||||
case:
|
||||
create_mode = win32.OPEN_EXISTING
|
||||
}
|
||||
|
||||
attrs := win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
|
||||
if mode & (O_NONBLOCK) == O_NONBLOCK {
|
||||
attrs |= win32.FILE_FLAG_OVERLAPPED
|
||||
}
|
||||
|
||||
wide_path := win32.utf8_to_wstring(path)
|
||||
handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil))
|
||||
handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, attrs, nil))
|
||||
if handle != INVALID_HANDLE {
|
||||
return handle, nil
|
||||
}
|
||||
@@ -862,4 +868,4 @@ pipe :: proc() -> (r, w: Handle, err: Error) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2207,7 +2207,7 @@ swizzle :: builtin.swizzle
|
||||
/*
|
||||
Extract the set of most-significant bits of a SIMD vector.
|
||||
|
||||
This procedure checks the the most-significant bit (MSB) for each lane of vector
|
||||
This procedure checks the most-significant bit (MSB) for each lane of vector
|
||||
and returns the numbers of lanes with the most-significant bit set. This procedure
|
||||
can be used in conjuction with `lanes_eq` (and other similar procedures) to
|
||||
count the number of matched lanes by computing the cardinality of the resulting
|
||||
@@ -2253,7 +2253,7 @@ extract_msbs :: intrinsics.simd_extract_msbs
|
||||
/*
|
||||
Extract the set of least-significant bits of a SIMD vector.
|
||||
|
||||
This procedure checks the the least-significant bit (LSB) for each lane of vector
|
||||
This procedure checks the least-significant bit (LSB) for each lane of vector
|
||||
and returns the numbers of lanes with the least-significant bit set. This procedure
|
||||
can be used in conjuction with `lanes_eq` (and other similar procedures) to
|
||||
count the number of matched lanes by computing the cardinality of the resulting
|
||||
|
||||
@@ -924,3 +924,39 @@ bitset_to_enum_slice_with_make :: proc(bs: $T, $E: typeid, allocator := context.
|
||||
}
|
||||
|
||||
bitset_to_enum_slice :: proc{bitset_to_enum_slice_with_make, bitset_to_enum_slice_with_buffer}
|
||||
|
||||
/*
|
||||
Removes the first n elements (`elems`) from a slice of slices, spanning inner slices and dropping empty ones.
|
||||
|
||||
If `elems` is out of bounds (more than the total) this will trigger a bounds check.
|
||||
|
||||
Example:
|
||||
import "core:fmt"
|
||||
import "core:slice"
|
||||
|
||||
advance_slices_example :: proc() {
|
||||
slices := [][]byte {
|
||||
{1, 2, 3, 4},
|
||||
{5, 6, 7},
|
||||
}
|
||||
|
||||
fmt.println(slice.advance_slices(slices, 4))
|
||||
}
|
||||
|
||||
Output:
|
||||
[[5, 6, 7]]
|
||||
*/
|
||||
advance_slices :: proc(slices: $S/[][]$T, elems: int) -> S {
|
||||
slices, elems := slices, elems
|
||||
for elems > 0 {
|
||||
slice := &slices[0]
|
||||
take := builtin.min(elems, len(slice))
|
||||
slice^ = slice[take:]
|
||||
if len(slice) == 0 {
|
||||
slices = slices[1:]
|
||||
}
|
||||
elems -= take
|
||||
}
|
||||
|
||||
return slices
|
||||
}
|
||||
|
||||
@@ -300,7 +300,9 @@ Example:
|
||||
import "core:slice"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
stable_sort_by_example :: proc() {
|
||||
Example :: struct { n: int, s: string }
|
||||
|
||||
arr := []Example {
|
||||
{2, "name"},
|
||||
{3, "Bill"},
|
||||
@@ -312,10 +314,9 @@ Example:
|
||||
})
|
||||
|
||||
for e in arr do fmt.printf("%s ", e.s)
|
||||
fmt.println()
|
||||
}
|
||||
|
||||
Example :: struct { n: int, s: string }
|
||||
|
||||
Output:
|
||||
My name is Bill
|
||||
*/
|
||||
@@ -335,7 +336,9 @@ Example:
|
||||
import "core:slice"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
stable_sort_by_cmp_example :: proc() {
|
||||
Example :: struct { n: int, s: string }
|
||||
|
||||
arr := []Example {
|
||||
{2, "name"},
|
||||
{3, "Bill"},
|
||||
@@ -347,9 +350,9 @@ Example:
|
||||
})
|
||||
|
||||
for e in arr do fmt.printf("%s ", e.s)
|
||||
fmt.println()
|
||||
}
|
||||
|
||||
Example :: struct { n: int, s: string }
|
||||
Output:
|
||||
My name is Bill
|
||||
*/
|
||||
|
||||
@@ -1166,7 +1166,7 @@ Example:
|
||||
import "core:sync/chan"
|
||||
import "core:fmt"
|
||||
|
||||
select_raw_example :: proc() {
|
||||
try_select_raw_example :: proc() {
|
||||
c, err := chan.create(chan.Chan(int), 1, context.allocator)
|
||||
assert(err == .None)
|
||||
defer chan.destroy(c)
|
||||
@@ -1198,11 +1198,11 @@ Example:
|
||||
|
||||
Output:
|
||||
|
||||
SELECT: 0 true
|
||||
SELECT: 0 Send
|
||||
RECEIVED VALUE 0
|
||||
SELECT: 0 true
|
||||
SELECT: 0 Recv
|
||||
RECEIVED VALUE 1
|
||||
SELECT: 0 false
|
||||
SELECT: -1 None
|
||||
|
||||
*/
|
||||
@(require_results)
|
||||
@@ -1219,7 +1219,7 @@ try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs
|
||||
count := 0
|
||||
|
||||
for c, i in recvs {
|
||||
if can_recv(c) {
|
||||
if !c.closed && can_recv(c) {
|
||||
candidates[count] = {
|
||||
is_recv = true,
|
||||
idx = i,
|
||||
@@ -1232,7 +1232,7 @@ try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs
|
||||
if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil {
|
||||
continue
|
||||
}
|
||||
if can_send(c) {
|
||||
if !c.closed && can_send(c) {
|
||||
candidates[count] = {
|
||||
is_recv = false,
|
||||
idx = i,
|
||||
|
||||
@@ -32,6 +32,7 @@ kevent :: proc(kq: KQ, change_list: []KEvent, event_list: []KEvent, timeout: ^po
|
||||
timeout,
|
||||
)
|
||||
if n_events == -1 {
|
||||
n_events = 0
|
||||
err = posix.errno()
|
||||
}
|
||||
return
|
||||
@@ -60,6 +61,7 @@ Filter :: enum _Filter_Backing {
|
||||
Proc = _FILTER_PROC, // Check for changes to the subject process.
|
||||
Signal = _FILTER_SIGNAL, // Check for signals delivered to the process.
|
||||
Timer = _FILTER_TIMER, // Timers.
|
||||
User = _FILTER_USER, // User events.
|
||||
}
|
||||
|
||||
RW_Flag :: enum u32 {
|
||||
@@ -82,18 +84,30 @@ Proc_Flag :: enum u32 {
|
||||
Exit = log2(0x80000000), // Process exited.
|
||||
Fork = log2(0x40000000), // Process forked.
|
||||
Exec = log2(0x20000000), // Process exec'd.
|
||||
Signal = log2(0x08000000), // Shared with `Filter.Signal`.
|
||||
}
|
||||
Proc_Flags :: bit_set[Proc_Flag; u32]
|
||||
|
||||
Timer_Flag :: enum u32 {
|
||||
Seconds = log2(0x00000001), // Data is seconds.
|
||||
USeconds = log2(0x00000002), // Data is microseconds.
|
||||
NSeconds = log2(_NOTE_NSECONDS), // Data is nanoseconds.
|
||||
USeconds = log2(_NOTE_USECONDS), // Data is microseconds.
|
||||
Absolute = log2(_NOTE_ABSOLUTE), // Absolute timeout.
|
||||
}
|
||||
Timer_Flags :: bit_set[Timer_Flag; u32]
|
||||
|
||||
User_Flag :: enum u32 {
|
||||
Trigger = log2(0x01000000),
|
||||
FFAnd = log2(0x40000000),
|
||||
FFOr = log2(0x80000000),
|
||||
}
|
||||
User_Flags :: bit_set[User_Flag; u32]
|
||||
|
||||
USER_FLAGS_COPY :: User_Flags{.FFOr, .FFAnd}
|
||||
USER_FLAGS_CONTROL_MASK :: transmute(User_Flags)u32(0xc0000000)
|
||||
USER_FLAGS_MASK :: transmute(User_Flags)u32(0x00FFFFFF)
|
||||
|
||||
// Data is nanoseconds.
|
||||
TIMER_FLAGS_NSECONDS :: _TIMER_FLAGS_NSECONDS
|
||||
|
||||
when ODIN_OS == .Darwin {
|
||||
|
||||
_Filter_Backing :: distinct i16
|
||||
@@ -106,10 +120,14 @@ when ODIN_OS == .Darwin {
|
||||
_FILTER_PROC :: -5
|
||||
_FILTER_SIGNAL :: -6
|
||||
_FILTER_TIMER :: -7
|
||||
_FILTER_USER :: -10
|
||||
|
||||
_NOTE_USECONDS :: 0x00000002
|
||||
_NOTE_NSECONDS :: 0x00000004
|
||||
_NOTE_ABSOLUTE :: 0x00000008
|
||||
|
||||
_TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}
|
||||
|
||||
KEvent :: struct #align(4) {
|
||||
// Value used to identify this event. The exact interpretation is determined by the attached filter.
|
||||
ident: uintptr,
|
||||
@@ -119,11 +137,12 @@ when ODIN_OS == .Darwin {
|
||||
flags: Flags,
|
||||
// Filter specific flags.
|
||||
fflags: struct #raw_union {
|
||||
rw: RW_Flags,
|
||||
vnode: VNode_Flags,
|
||||
fproc: Proc_Flags,
|
||||
rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
|
||||
vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
|
||||
fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
|
||||
// vm: VM_Flags,
|
||||
timer: Timer_Flags,
|
||||
timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
|
||||
user: User_Flags `raw_union_tag:"filter=.User"`,
|
||||
},
|
||||
// Filter specific data.
|
||||
data: c.long /* intptr_t */,
|
||||
@@ -143,9 +162,13 @@ when ODIN_OS == .Darwin {
|
||||
_FILTER_PROC :: -5
|
||||
_FILTER_SIGNAL :: -6
|
||||
_FILTER_TIMER :: -7
|
||||
_FILTER_USER :: -11
|
||||
|
||||
_NOTE_NSECONDS :: 0x00000004
|
||||
_NOTE_ABSOLUTE :: 0x00000008
|
||||
_NOTE_USECONDS :: 0x00000004
|
||||
_NOTE_NSECONDS :: 0x00000008
|
||||
_NOTE_ABSOLUTE :: 0x00000010
|
||||
|
||||
_TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}
|
||||
|
||||
KEvent :: struct {
|
||||
// Value used to identify this event. The exact interpretation is determined by the attached filter.
|
||||
@@ -156,11 +179,12 @@ when ODIN_OS == .Darwin {
|
||||
flags: Flags,
|
||||
// Filter specific flags.
|
||||
fflags: struct #raw_union {
|
||||
rw: RW_Flags,
|
||||
vnode: VNode_Flags,
|
||||
fproc: Proc_Flags,
|
||||
rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
|
||||
vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
|
||||
fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
|
||||
// vm: VM_Flags,
|
||||
timer: Timer_Flags,
|
||||
timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
|
||||
user: User_Flags `raw_union_tag:"filter=.User"`,
|
||||
},
|
||||
// Filter specific data.
|
||||
data: i64,
|
||||
@@ -181,11 +205,14 @@ when ODIN_OS == .Darwin {
|
||||
_FILTER_PROC :: 4
|
||||
_FILTER_SIGNAL :: 5
|
||||
_FILTER_TIMER :: 6
|
||||
_FILTER_USER :: 8
|
||||
|
||||
_NOTE_NSECONDS :: 0x00000003
|
||||
_NOTE_USECONDS :: 0x00000002
|
||||
_NOTE_ABSOLUTE :: 0x00000010
|
||||
|
||||
KEvent :: struct #align(4) {
|
||||
_TIMER_FLAGS_NSECONDS :: Timer_Flags{.Seconds, .USeconds}
|
||||
|
||||
KEvent :: struct {
|
||||
// Value used to identify this event. The exact interpretation is determined by the attached filter.
|
||||
ident: uintptr,
|
||||
// Filter for event.
|
||||
@@ -194,18 +221,17 @@ when ODIN_OS == .Darwin {
|
||||
flags: Flags,
|
||||
// Filter specific flags.
|
||||
fflags: struct #raw_union {
|
||||
rw: RW_Flags,
|
||||
vnode: VNode_Flags,
|
||||
fproc: Proc_Flags,
|
||||
rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
|
||||
vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
|
||||
fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
|
||||
// vm: VM_Flags,
|
||||
timer: Timer_Flags,
|
||||
timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
|
||||
user: User_Flags `raw_union_tag:"filter=.User"`,
|
||||
},
|
||||
// Filter specific data.
|
||||
data: i64,
|
||||
// Opaque user data passed through the kernel unchanged.
|
||||
udata: rawptr,
|
||||
// Extensions.
|
||||
ext: [4]u64,
|
||||
}
|
||||
} else when ODIN_OS == .OpenBSD {
|
||||
|
||||
@@ -219,10 +245,14 @@ when ODIN_OS == .Darwin {
|
||||
_FILTER_PROC :: -5
|
||||
_FILTER_SIGNAL :: -6
|
||||
_FILTER_TIMER :: -7
|
||||
_FILTER_USER :: -10
|
||||
|
||||
_NOTE_NSECONDS :: 0x00000003
|
||||
_NOTE_USECONDS :: 0x00000002
|
||||
_NOTE_NSECONDS :: 0x00000004
|
||||
_NOTE_ABSOLUTE :: 0x00000010
|
||||
|
||||
_TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))}
|
||||
|
||||
KEvent :: struct #align(4) {
|
||||
// Value used to identify this event. The exact interpretation is determined by the attached filter.
|
||||
ident: uintptr,
|
||||
@@ -232,11 +262,12 @@ when ODIN_OS == .Darwin {
|
||||
flags: Flags,
|
||||
// Filter specific flags.
|
||||
fflags: struct #raw_union {
|
||||
rw: RW_Flags,
|
||||
vnode: VNode_Flags,
|
||||
fproc: Proc_Flags,
|
||||
rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`,
|
||||
vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`,
|
||||
fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`,
|
||||
// vm: VM_Flags,
|
||||
timer: Timer_Flags,
|
||||
timer: Timer_Flags `raw_union_tag:"filter=.Timer"`,
|
||||
user: User_Flags `raw_union_tag:"filter=.User"`,
|
||||
},
|
||||
// Filter specific data.
|
||||
data: i64,
|
||||
@@ -245,12 +276,20 @@ when ODIN_OS == .Darwin {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .NetBSD {
|
||||
@(private)
|
||||
LKEVENT :: "__kevent50"
|
||||
} else {
|
||||
@(private)
|
||||
LKEVENT :: "kevent"
|
||||
}
|
||||
|
||||
@(private)
|
||||
log2 :: intrinsics.constant_log2
|
||||
|
||||
foreign lib {
|
||||
@(link_name="kqueue")
|
||||
_kqueue :: proc() -> KQ ---
|
||||
@(link_name="kevent")
|
||||
@(link_name=LKEVENT)
|
||||
_kevent :: proc(kq: KQ, change_list: [^]KEvent, n_changes: c.int, event_list: [^]KEvent, n_events: c.int, timeout: ^posix.timespec) -> c.int ---
|
||||
}
|
||||
|
||||
@@ -2270,6 +2270,12 @@ Swap_Flags_Bits :: enum {
|
||||
DISCARD = log2(0x10000),
|
||||
}
|
||||
|
||||
Eventfd_Flags_Bits :: enum {
|
||||
SEMAPHORE,
|
||||
CLOEXEC = auto_cast Open_Flags_Bits.CLOEXEC,
|
||||
NONBLOCK = auto_cast Open_Flags_Bits.NONBLOCK,
|
||||
}
|
||||
|
||||
Sched_Policy :: enum u32 {
|
||||
OTHER = 0,
|
||||
BATCH = 3,
|
||||
|
||||
@@ -3070,7 +3070,10 @@ timerfd_create :: proc "contextless" (clock_id: Clock_Id, flags: Open_Flags) ->
|
||||
return errno_unwrap2(ret, Fd)
|
||||
}
|
||||
|
||||
// TODO(flysand): eventfd
|
||||
eventfd :: proc "contextless" (initval: u32, flags: Eventfd_Flags) -> (Fd, Errno) {
|
||||
ret := syscall(SYS_eventfd2, initval, transmute(i32)flags)
|
||||
return errno_unwrap2(ret, Fd)
|
||||
}
|
||||
|
||||
// TODO(flysand): fallocate
|
||||
|
||||
|
||||
@@ -1589,23 +1589,23 @@ IO_Uring_SQE :: struct {
|
||||
using __ioprio: struct #raw_union {
|
||||
ioprio: u16,
|
||||
sq_accept_flags: IO_Uring_Accept_Flags,
|
||||
sq_send_recv_flags: IO_Uring_Send_Recv_Flags,
|
||||
sq_send_recv_flags: IO_Uring_Send_Recv_Flags `raw_union_tag:"opcode=.SENDMSG, opcode=.RECVMSG, opcode=.SEND, opcode=.RECV"`,
|
||||
},
|
||||
fd: Fd,
|
||||
using __offset: struct #raw_union {
|
||||
// Offset into file.
|
||||
off: u64,
|
||||
addr2: u64,
|
||||
off: u64 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.SPLICE, opcode=.POLL_REMOVE, opcode=.EPOLL_CTL, opcode=.TIMEOUT, opcode=.ACCEPT, opcode=.CONNECT, opcode=.READ, opcode=.WRITE, opcode=.FILES_UPDATE, opcode=.SOCKET"`,
|
||||
addr2: u64 `raw_union_tag:"opcode=.SEND, opcode=.BIND"`,
|
||||
using _: struct {
|
||||
cmd_op: u32,
|
||||
__pad1: u32,
|
||||
},
|
||||
statx: ^Statx,
|
||||
statx: ^Statx `raw_union_tag:"opcode=.STATX"`,
|
||||
},
|
||||
using __iovecs: struct #raw_union {
|
||||
// Pointer to buffer or iovecs.
|
||||
addr: u64,
|
||||
splice_off_in: u64,
|
||||
addr: u64 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.POLL_REMOVE, opcode=.EPOLL_CTL, opcode=.SENDMSG, opcode=.RECVMSG, opcode=.SEND, opcode=.RECV, opcode=.TIMEOUT, opcode=.TIMEOUT_REMOVE, opcode=.ACCEPT, opcode=.ASYNC_CANCEL, opcode=.LINK_TIMEOUT, opcode=.CONNECT, opcode=.MADVISE, opcode=.OPENAT, opcode=.STATX, opcode=.READ, opcode=.WRITE, opcode=.FILES_UPDATE, opcode=.BIND, opcode=.LISTEN"`,
|
||||
splice_off_in: u64 `raw_union_tag:"opcode=.SPLICE"`,
|
||||
using _: struct {
|
||||
level: u32,
|
||||
optname: u32,
|
||||
@@ -1613,28 +1613,28 @@ IO_Uring_SQE :: struct {
|
||||
},
|
||||
using __len: struct #raw_union {
|
||||
// Buffer size or number of iovecs.
|
||||
len: u32,
|
||||
poll_flags: IO_Uring_Poll_Add_Flags,
|
||||
statx_mask: Statx_Mask,
|
||||
epoll_ctl_op: EPoll_Ctl_Opcode,
|
||||
shutdown_how: Shutdown_How,
|
||||
len: u32 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.SPLICE, opcode=.SEND, opcode=.RECV, opcode=.TIMEOUT, opcode=.LINK_TIMEOUT, opcode=.MADVISE, opcode=.OPENAT, opcode=.READ, opcode=.WRITE, opcode=.TEE, opcode=.FILES_UPDATE, opcode=.SOCKET"`,
|
||||
poll_flags: IO_Uring_Poll_Add_Flags `raw_union_tag:"opcode=.POLL_ADD, opcode=.POLL_REMOVE"`,
|
||||
statx_mask: Statx_Mask `raw_union_tag:"opcode=.STATX"`,
|
||||
epoll_ctl_op: EPoll_Ctl_Opcode `raw_union_tag:"opcode=.EPOLL_CTL"`,
|
||||
shutdown_how: Shutdown_How `raw_union_tag:"opcode=.SHUTDOWN"`,
|
||||
},
|
||||
using __contents: struct #raw_union {
|
||||
rw_flags: i32,
|
||||
fsync_flags: IO_Uring_Fsync_Flags,
|
||||
rw_flags: i32 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.SOCKET"`,
|
||||
fsync_flags: IO_Uring_Fsync_Flags `raw_union_tag:"opcode=.FSYNC"`,
|
||||
// compatibility.
|
||||
poll_events: Fd_Poll_Events,
|
||||
poll_events: Fd_Poll_Events `raw_union_tag:"opcode=.POLL_ADD, opcode=.POLL_REMOVE"`,
|
||||
// word-reversed for BE.
|
||||
poll32_events: u32,
|
||||
sync_range_flags: u32,
|
||||
msg_flags: Socket_Msg,
|
||||
timeout_flags: IO_Uring_Timeout_Flags,
|
||||
accept_flags: Socket_FD_Flags,
|
||||
msg_flags: Socket_Msg `raw_union_tag:"opcode=.SENDMSG, opcode=.RECVMSG, opcode=.SEND, opcode=.RECV"`,
|
||||
timeout_flags: IO_Uring_Timeout_Flags `raw_union_tag:"opcode=.TIMEOUT, opcode=.TIMEOUT_REMOVE, opcode=.LINK_TIMEOUT"`,
|
||||
accept_flags: Socket_FD_Flags `raw_union_tag:"opcode=.ACCEPT"`,
|
||||
cancel_flags: u32,
|
||||
open_flags: Open_Flags,
|
||||
statx_flags: FD_Flags,
|
||||
fadvise_advice: u32,
|
||||
splice_flags: IO_Uring_Splice_Flags,
|
||||
open_flags: Open_Flags `raw_union_tag:"opcode=.OPENAT"`,
|
||||
statx_flags: FD_Flags `raw_union_tag:"opcode=.STATX"`,
|
||||
fadvise_advice: u32 `raw_union_tag:"opcode=.MADVISE"`,
|
||||
splice_flags: IO_Uring_Splice_Flags `raw_union_tag:"opcode=.SPLICE, opcode=.TEE"`,
|
||||
rename_flags: u32,
|
||||
unlink_flags: u32,
|
||||
hardlink_flags: u32,
|
||||
@@ -1653,10 +1653,10 @@ IO_Uring_SQE :: struct {
|
||||
// Personality to use, if used.
|
||||
personality: u16,
|
||||
using _: struct #raw_union {
|
||||
splice_fd_in: Fd,
|
||||
file_index: u32,
|
||||
splice_fd_in: Fd `raw_union_tag:"opcode=.SPLICE, opcode=.TEE"`,
|
||||
file_index: u32 `raw_union_tag:"opcode=.ACCEPT, opcode=.OPENAT, opcode=.CLOSE, opcode=.SOCKET"`,
|
||||
using _: struct {
|
||||
addr_len: u16,
|
||||
addr_len: u16 `raw_union_tag:"opcode=.SEND"`,
|
||||
__pad3: [1]u16,
|
||||
},
|
||||
},
|
||||
@@ -1749,6 +1749,8 @@ Umount2_Flags :: bit_set[Umount2_Flags_Bits; u32]
|
||||
|
||||
Swap_Flags :: bit_set[Swap_Flags_Bits; u32]
|
||||
|
||||
Eventfd_Flags :: bit_set[Eventfd_Flags_Bits; i32]
|
||||
|
||||
Cpu_Set :: bit_set[0 ..< 128]
|
||||
|
||||
Sched_Param :: struct {
|
||||
|
||||
88
core/sys/linux/uring/doc.odin
Normal file
88
core/sys/linux/uring/doc.odin
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Wrapper/convenience package over the raw io_uring syscalls, providing help with setup, creation, and operating the ring.
|
||||
|
||||
The following example shows a simple `cat` program implementation using the package.
|
||||
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:sys/linux"
|
||||
import "core:sys/linux/uring"
|
||||
|
||||
Request :: struct {
|
||||
path: cstring,
|
||||
buffer: []byte,
|
||||
completion: linux.IO_Uring_CQE,
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
if len(os.args) < 2 {
|
||||
fmt.eprintfln("Usage: %s [file name] <[file name] ...>", os.args[0])
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
requests := make_soa(#soa []Request, len(os.args)-1)
|
||||
defer delete(requests)
|
||||
|
||||
ring: uring.Ring
|
||||
params := uring.DEFAULT_PARAMS
|
||||
err := uring.init(&ring, ¶ms)
|
||||
fmt.assertf(err == nil, "uring.init: %v", err)
|
||||
defer uring.destroy(&ring)
|
||||
|
||||
for &request, i in requests {
|
||||
request.path = runtime.args__[i+1]
|
||||
// sets up a read requests and adds it to the ring buffer.
|
||||
submit_read_request(request.path, &request.buffer, &ring)
|
||||
}
|
||||
|
||||
ulen := u32(len(requests))
|
||||
|
||||
// submit the requests and wait for them to complete right away.
|
||||
n, serr := uring.submit(&ring, ulen)
|
||||
fmt.assertf(serr == nil, "uring.submit: %v", serr)
|
||||
assert(n == ulen)
|
||||
|
||||
// copy the completed requests out of the ring buffer.
|
||||
cn := uring.copy_cqes_ready(&ring, requests.completion[:ulen])
|
||||
assert(cn == ulen)
|
||||
|
||||
for request in requests {
|
||||
// check result of the requests.
|
||||
fmt.assertf(request.completion.res >= 0, "read %q failed: %v", request.path, linux.Errno(-request.completion.res))
|
||||
// print out.
|
||||
fmt.print(string(request.buffer))
|
||||
|
||||
delete(request.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
submit_read_request :: proc(path: cstring, buffer: ^[]byte, ring: ^uring.Ring) {
|
||||
fd, err := linux.open(path, {})
|
||||
fmt.assertf(err == nil, "open(%q): %v", path, err)
|
||||
|
||||
file_sz := get_file_size(fd)
|
||||
|
||||
buffer^ = make([]byte, file_sz)
|
||||
|
||||
_, ok := uring.read(ring, 0, fd, buffer^, 0)
|
||||
assert(ok, "could not get read sqe")
|
||||
}
|
||||
|
||||
get_file_size :: proc(fd: linux.Fd) -> uint {
|
||||
st: linux.Stat
|
||||
err := linux.fstat(fd, &st)
|
||||
fmt.assertf(err == nil, "fstat: %v", err)
|
||||
|
||||
if linux.S_ISREG(st.mode) {
|
||||
return uint(st.size)
|
||||
}
|
||||
|
||||
panic("not a regular file")
|
||||
}
|
||||
*/
|
||||
package uring
|
||||
847
core/sys/linux/uring/ops.odin
Normal file
847
core/sys/linux/uring/ops.odin
Normal file
@@ -0,0 +1,847 @@
|
||||
package uring
|
||||
|
||||
import "core:sys/linux"
|
||||
|
||||
// Do not perform any I/O. This is useful for testing the performance of the uring implementation itself.
|
||||
nop :: proc(ring: ^Ring, user_data: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .NOP
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// Vectored read operation, see also readv(2).
|
||||
readv :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, iovs: []linux.IO_Vec, off: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .READV
|
||||
sqe.fd = fd
|
||||
sqe.addr = cast(u64)uintptr(raw_data(iovs))
|
||||
sqe.len = u32(len(iovs))
|
||||
sqe.off = off
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// Vectored write operation, see also writev(2).
|
||||
writev :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, iovs: []linux.IO_Vec, off: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .WRITEV
|
||||
sqe.fd = fd
|
||||
sqe.addr = cast(u64)uintptr(raw_data(iovs))
|
||||
sqe.len = u32(len(iovs))
|
||||
sqe.off = off
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
read_fixed :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
write_fixed :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
/*
|
||||
File sync. See also fsync(2).
|
||||
|
||||
Optionally off and len can be used to specify a range within the file to be synced rather than syncing the entire file, which is the default behavior.
|
||||
|
||||
Note that, while I/O is initiated in the order in which it appears in the submission queue, completions are unordered.
|
||||
For example, an application which places a write I/O followed by an fsync in the submission queue cannot expect the fsync to apply to the write.
|
||||
The two operations execute in parallel, so the fsync may complete before the write is issued to the storage.
|
||||
The same is also true for previously issued writes that have not completed prior to the fsync.
|
||||
To enforce ordering one may utilize linked SQEs,
|
||||
IOSQE_IO_DRAIN or wait for the arrival of CQEs of requests which have to be ordered before a given request before submitting its SQE.
|
||||
*/
|
||||
fsync :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, flags: linux.IO_Uring_Fsync_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .FSYNC
|
||||
sqe.fsync_flags = flags
|
||||
sqe.fd = fd
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Poll the fd specified in the submission queue entry for the events specified in the poll_events field.
|
||||
|
||||
Unlike poll or epoll without EPOLLONESHOT, by default this interface always works in one shot mode.
|
||||
That is, once the poll operation is completed, it will have to be resubmitted.
|
||||
|
||||
If IORING_POLL_ADD_MULTI is set in the SQE len field, then the poll will work in multi shot mode instead.
|
||||
That means it'll repatedly trigger when the requested event becomes true, and hence multiple CQEs can be generated from this single SQE.
|
||||
The CQE flags field will have IORING_CQE_F_MORE set on completion if the application should expect further CQE entries from the original request.
|
||||
If this flag isn't set on completion, then the poll request has been terminated and no further events will be generated.
|
||||
This mode is available since 5.13.
|
||||
|
||||
This command works like an async poll(2) and the completion event result is the returned mask of events.
|
||||
|
||||
Without IORING_POLL_ADD_MULTI and the initial poll operation with IORING_POLL_ADD_MULTI the operation is level triggered,
|
||||
i.e. if there is data ready or events pending etc.
|
||||
at the time of submission a corresponding CQE will be posted.
|
||||
Potential further completions beyond the first caused by a IORING_POLL_ADD_MULTI are edge triggered.
|
||||
*/
|
||||
poll_add :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, events: linux.Fd_Poll_Events, flags: linux.IO_Uring_Poll_Add_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .POLL_ADD
|
||||
sqe.fd = fd
|
||||
sqe.poll_events = events
|
||||
sqe.poll_flags = flags
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Remove an existing poll request.
|
||||
|
||||
If found, the res field of the struct io_uring_cqe will contain 0.
|
||||
If not found, res will contain -ENOENT, or -EALREADY if the poll request was in the process of completing already.
|
||||
*/
|
||||
poll_remove :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, events: linux.Fd_Poll_Events) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .POLL_REMOVE
|
||||
sqe.fd = fd
|
||||
sqe.poll_events = events
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Update the events of an existing poll request.
|
||||
|
||||
The request will update an existing poll request with the mask of events passed in with this request.
|
||||
The lookup is based on the user_data field of the original SQE submitted.
|
||||
|
||||
Updating an existing poll is available since 5.13.
|
||||
*/
|
||||
poll_update_events :: proc(ring: ^Ring, user_data: u64, orig_user_data: u64, fd: linux.Fd, events: linux.Fd_Poll_Events) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .POLL_REMOVE
|
||||
sqe.fd = fd
|
||||
sqe.addr = orig_user_data
|
||||
sqe.poll_events = events
|
||||
sqe.user_data = user_data
|
||||
sqe.poll_flags = {.UPDATE_EVENTS}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Update the user data of an existing poll request.
|
||||
|
||||
The request will update the user_data of an existing poll request based on the value passed.
|
||||
|
||||
Updating an existing poll is available since 5.13.
|
||||
*/
|
||||
poll_update_user_data :: proc(ring: ^Ring, user_data: u64, orig_user_data: u64, new_user_data: u64, fd: linux.Fd) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .POLL_REMOVE
|
||||
sqe.fd = fd
|
||||
sqe.off = orig_user_data
|
||||
sqe.addr = new_user_data
|
||||
sqe.user_data = user_data
|
||||
sqe.poll_flags = {.UPDATE_USER_DATA}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Add, remove or modify entries in the interest list of epoll(7).
|
||||
|
||||
See epoll_ctl(2) for details of the system call.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
epoll_ctl :: proc(ring: ^Ring, user_data: u64, epfd: linux.Fd, op: linux.EPoll_Ctl_Opcode, fd: linux.Fd, event: ^linux.EPoll_Event) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .EPOLL_CTL
|
||||
sqe.fd = epfd
|
||||
sqe.off = u64(fd)
|
||||
sqe.epoll_ctl_op = op
|
||||
sqe.addr = cast(u64)uintptr(event)
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
sync_file_range :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a sendmsg(2) system call.
|
||||
|
||||
See also sendmsg(2) for the general description of the related system call.
|
||||
|
||||
poll_first: if set, uring will assume the socket is currently full and attempting to send data will be unsuccessful.
|
||||
For this case, uring will arm internal poll and trigger a send of the data when there is enough space available.
|
||||
This initial send attempt can be wasteful for the case where the socket is expected to be full, setting this flag will
|
||||
bypass the initial send attempt and go straight to arming poll.
|
||||
If poll does indicate that data can be sent, the operation will proceed.
|
||||
|
||||
Available since 5.3.
|
||||
*/
|
||||
sendmsg :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, msghdr: ^linux.Msg_Hdr, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .SENDMSG
|
||||
sqe.fd = fd
|
||||
sqe.addr = cast(u64)uintptr(msghdr)
|
||||
sqe.msg_flags = flags
|
||||
sqe.user_data = user_data
|
||||
sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Works just like sendmsg, but receives instead of sends.
|
||||
|
||||
poll_first: If set, uring will assume the socket is currently empty and attempting to receive data will be unsuccessful.
|
||||
For this case, uring will arm internal poll and trigger a receive of the data when the socket has data to be read.
|
||||
This initial receive attempt can be wasteful for the case where the socket is expected to be empty, setting this flag will bypass the initial receive attempt and go straight to arming poll.
|
||||
If poll does indicate that data is ready to be received, the operation will proceed.
|
||||
|
||||
Available since 5.3.
|
||||
*/
|
||||
recvmsg :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, msghdr: ^linux.Msg_Hdr, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .RECVMSG
|
||||
sqe.fd = fd
|
||||
sqe.addr = cast(u64)uintptr(msghdr)
|
||||
sqe.msg_flags = flags
|
||||
sqe.user_data = user_data
|
||||
sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a send(2) system call.
|
||||
|
||||
See also send(2) for the general description of the related system call.
|
||||
|
||||
poll_first: If set, uring will assume the socket is currently full and attempting to send data will be unsuccessful.
|
||||
For this case, uring will arm internal poll and trigger a send of the data when there is enough space available.
|
||||
This initial send attempt can be wasteful for the case where the socket is expected to be full, setting this flag will bypass the initial send attempt and go straight to arming poll.
|
||||
If poll does indicate that data can be sent, the operation will proceed.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
send :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, buf: []byte, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .SEND
|
||||
sqe.fd = sockfd
|
||||
sqe.addr = cast(u64)uintptr(raw_data(buf))
|
||||
sqe.len = u32(len(buf))
|
||||
sqe.msg_flags = flags
|
||||
sqe.user_data = user_data
|
||||
sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
sendto :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, buf: []byte, flags: linux.Socket_Msg, dest: ^$T, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool)
|
||||
where T == linux.Sock_Addr_In || T == linux.Sock_Addr_In6 || T == linux.Sock_Addr_Un || T == linux.Sock_Addr_Any {
|
||||
|
||||
sqe = send(ring, user_data, sockfd, buf, flags, poll_first) or_return
|
||||
sqe.addr2 = u64(uintptr(dest))
|
||||
sqe.addr_len = u16(size_of(T))
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Works just like send, but receives instead of sends.
|
||||
|
||||
poll_first: If set, uring will assume the socket is currently empty and attempting to receive data will be unsuccessful.
|
||||
For this case, uring will arm internal poll and trigger a receive of the data when the socket has data to be read.
|
||||
This initial receive attempt can be wasteful for the case where the socket is expected to be empty, setting this flag will bypass the initial receive attempt and go straight to arming poll.
|
||||
If poll does indicate that data is ready to be received, the operation will proceed.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
recv :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, buf: []byte, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .RECV
|
||||
sqe.fd = sockfd
|
||||
sqe.addr = cast(u64)uintptr(raw_data(buf))
|
||||
sqe.len = cast(u32)uintptr(len(buf))
|
||||
sqe.msg_flags = flags
|
||||
sqe.user_data = user_data
|
||||
sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Register a timeout operation.
|
||||
|
||||
The timeout will complete when either the timeout expires, or after the specified number of
|
||||
events complete (if `count` is greater than `0`).
|
||||
|
||||
`flags` may be `0` for a relative timeout, or `IORING_TIMEOUT_ABS` for an absolute timeout.
|
||||
|
||||
The completion event result will be `-ETIME` if the timeout completed through expiration,
|
||||
`0` if the timeout completed after the specified number of events, or `-ECANCELED` if the
|
||||
timeout was removed before it expired.
|
||||
|
||||
uring timeouts use the `CLOCK.MONOTONIC` clock source.
|
||||
*/
|
||||
timeout :: proc(ring: ^Ring, user_data: u64, ts: ^linux.Time_Spec, count: u32, flags: linux.IO_Uring_Timeout_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .TIMEOUT
|
||||
sqe.fd = -1
|
||||
sqe.addr = cast(u64)uintptr(ts)
|
||||
sqe.len = 1
|
||||
sqe.off = u64(count)
|
||||
sqe.timeout_flags = flags
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Rmove an existing timeout operation.
|
||||
|
||||
The timeout is identified by it's `user_data`.
|
||||
|
||||
The completion event result will be `0` if the timeout was found and cancelled successfully,
|
||||
`-EBUSY` if the timeout was found but expiration was already in progress, or
|
||||
`-ENOENT` if the timeout was not found.
|
||||
*/
|
||||
timeout_remove :: proc(ring: ^Ring, user_data: u64, timeout_user_data: u64, flags: linux.IO_Uring_Timeout_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .TIMEOUT_REMOVE
|
||||
sqe.fd = -1
|
||||
sqe.addr = timeout_user_data
|
||||
sqe.timeout_flags = flags
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of an accept4(2) system call.
|
||||
|
||||
See also accept4(2) for the general description of the related system call.
|
||||
|
||||
If the file_index field is set to a positive number, the file won't be installed into the normal file table as usual
|
||||
but will be placed into the fixed file table at index file_index - 1.
|
||||
In this case, instead of returning a file descriptor, the result will contain either 0 on success or an error.
|
||||
If the index points to a valid empty slot, the installation is guaranteed to not fail.
|
||||
If there is already a file in the slot, it will be replaced, similar to IORING_OP_FILES_UPDATE.
|
||||
Please note that only uring has access to such files and no other syscall can use them. See IOSQE_FIXED_FILE and IORING_REGISTER_FILES.
|
||||
|
||||
Available since 5.5.
|
||||
*/
|
||||
accept :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, addr: ^$T, addr_len: ^i32, flags: linux.Socket_FD_Flags, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool)
|
||||
where T == linux.Sock_Addr_In || T == linux.Sock_Addr_In6 || T == linux.Sock_Addr_Un || T == linux.Sock_Addr_Any {
|
||||
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .ACCEPT
|
||||
sqe.fd = sockfd
|
||||
sqe.addr = cast(u64)uintptr(addr)
|
||||
sqe.off = cast(u64)uintptr(addr_len)
|
||||
sqe.accept_flags = flags
|
||||
sqe.user_data = user_data
|
||||
sqe.file_index = file_index
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Attempt to cancel an already issued request.
|
||||
|
||||
The request is identified by it's user data.
|
||||
|
||||
The cancelation request will complete with one of the following results codes.
|
||||
|
||||
If found, the res field of the cqe will contain 0.
|
||||
If not found, res will contain -ENOENT.
|
||||
|
||||
If found and attempted canceled, the res field will contain -EALREADY.
|
||||
In this case, the request may or may not terminate.
|
||||
In general, requests that are interruptible (like socket IO) will get canceled, while disk IO requests cannot be canceled if already started.
|
||||
|
||||
Available since 5.5.
|
||||
*/
|
||||
async_cancel :: proc(ring: ^Ring, orig_user_data: u64, user_data: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .ASYNC_CANCEL
|
||||
sqe.addr = orig_user_data
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Adds a link timeout operation.
|
||||
|
||||
You need to set linux.IOSQE_IO_LINK to flags of the target operation
|
||||
and then call this method right after the target operation.
|
||||
See https://lwn.net/Articles/803932/ for detail.
|
||||
|
||||
If the dependent request finishes before the linked timeout, the timeout
|
||||
is canceled. If the timeout finishes before the dependent request, the
|
||||
dependent request will be canceled.
|
||||
|
||||
The completion event result of the link_timeout will be
|
||||
`-ETIME` if the timeout finishes before the dependent request
|
||||
(in this case, the completion event result of the dependent request will
|
||||
be `-ECANCELED`), or
|
||||
`-EALREADY` if the dependent request finishes before the linked timeout.
|
||||
|
||||
Available since 5.5.
|
||||
*/
|
||||
link_timeout :: proc(ring: ^Ring, user_data: u64, ts: ^linux.Time_Spec, flags: linux.IO_Uring_Timeout_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring, 0) or_return
|
||||
sqe.opcode = .LINK_TIMEOUT
|
||||
sqe.fd = -1
|
||||
sqe.addr = cast(u64)uintptr(ts)
|
||||
sqe.len = 1
|
||||
sqe.timeout_flags = flags
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a connect(2) system call.
|
||||
|
||||
See also connect(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.5.
|
||||
*/
|
||||
connect :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, addr: ^$T) -> (sqe: ^linux.IO_Uring_SQE, ok: bool)
|
||||
where T == linux.Sock_Addr_In || T == linux.Sock_Addr_In6 || T == linux.Sock_Addr_Un || T == linux.Sock_Addr_Any {
|
||||
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .CONNECT
|
||||
sqe.fd = sockfd
|
||||
sqe.addr = cast(u64)uintptr(addr)
|
||||
sqe.off = size_of(T)
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
fallocate :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
fadvise :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a madvise(2) system call.
|
||||
|
||||
See also madvise(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
madvise :: proc(ring: ^Ring, user_data: u64, addr: rawptr, size: u32, advise: linux.MAdvice) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .MADVISE
|
||||
sqe.addr = u64(uintptr(addr))
|
||||
sqe.len = size
|
||||
sqe.fadvise_advice = cast(u32)transmute(int)advise
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a openat(2) system call.
|
||||
|
||||
See also openat(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.6.
|
||||
|
||||
If the file_index is set to a positive number,
|
||||
the file won't be installed into the normal file table as usual but will be placed into the fixed file table at index file_index - 1.
|
||||
In this case, instead of returning a file descriptor, the result will contain either 0 on success or an error.
|
||||
If the index points to a valid empty slot, the installation is guaranteed to not fail.
|
||||
If there is already a file in the slot, it will be replaced, similar to IORING_OP_FILES_UPDATE.
|
||||
Please note that only uring has access to such files and no other syscall can use them.
|
||||
See IOSQE_FIXED_FILE and IORING_REGISTER_FILES.
|
||||
|
||||
Available since 5.15.
|
||||
*/
|
||||
openat :: proc(ring: ^Ring, user_data: u64, dirfd: linux.Fd, path: cstring, mode: linux.Mode, flags: linux.Open_Flags, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .OPENAT
|
||||
sqe.fd = dirfd
|
||||
sqe.addr = cast(u64)transmute(uintptr)path
|
||||
sqe.len = transmute(u32)mode
|
||||
sqe.open_flags = flags
|
||||
sqe.user_data = user_data
|
||||
sqe.file_index = file_index
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
openat2 :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a close(2) system call.
|
||||
|
||||
See also close(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.6.
|
||||
|
||||
If the file_index field is set to a positive number, this command can be used to close files that were
|
||||
direct opened through IORING_OP_OPENAT, IORING_OP_OPENAT2, or IORING_OP_ACCEPT using the uring specific direct descriptors.
|
||||
Note that only one of the descriptor fields may be set.
|
||||
The direct close feature is available since the 5.15 kernel, where direct descriptors were introduced.
|
||||
*/
|
||||
close :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .CLOSE
|
||||
sqe.fd = fd
|
||||
sqe.user_data = user_data
|
||||
sqe.file_index = file_index
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a statx(2) system call.
|
||||
|
||||
See also statx(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
statx :: proc(ring: ^Ring, user_data: u64, dirfd: linux.Fd, pathname: cstring, flags: linux.FD_Flags, mask: linux.Statx_Mask, buf: ^linux.Statx) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .STATX
|
||||
sqe.fd = dirfd
|
||||
sqe.addr = cast(u64)transmute(uintptr)pathname
|
||||
sqe.statx_flags = flags
|
||||
sqe.statx_mask = mask
|
||||
sqe.statx = buf
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a pread(2) system call.
|
||||
|
||||
If offset is set to -1 , the offset will use (and advance) the file position, like the read(2) system calls.
|
||||
These are non-vectored versions of the IORING_OP_READV and IORING_OP_WRITEV opcodes.
|
||||
See also read(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
read :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, buf: []u8, offset: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .READ
|
||||
sqe.fd = fd
|
||||
sqe.addr = cast(u64)uintptr(raw_data(buf))
|
||||
sqe.len = u32(len(buf))
|
||||
sqe.off = offset
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a pwrite(2) system call.
|
||||
|
||||
If offset is set to -1 , the offset will use (and advance) the file position, like the read(2) system calls.
|
||||
These are non-vectored versions of the IORING_OP_READV and IORING_OP_WRITEV opcodes.
|
||||
See also write(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
write :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, buf: []u8, offset: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .WRITE
|
||||
sqe.fd = fd
|
||||
sqe.addr = cast(u64)uintptr(raw_data(buf))
|
||||
sqe.len = u32(len(buf))
|
||||
sqe.off = offset
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a splice(2) system call.
|
||||
|
||||
A sentinel value of -1 is used to pass the equivalent of a NULL for the offsets to splice(2).
|
||||
|
||||
Please note that one of the file descriptors must refer to a pipe.
|
||||
See also splice(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.7.
|
||||
|
||||
*/
|
||||
splice :: proc(ring: ^Ring, user_data: u64, fd_in: linux.Fd, off_in: i64, fd_out: linux.Fd, off_out: i64, len: u32, flags: linux.IO_Uring_Splice_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .SPLICE
|
||||
sqe.splice_fd_in = fd_in
|
||||
sqe.splice_off_in = cast(u64)off_in
|
||||
sqe.fd = fd_out
|
||||
sqe.off = cast(u64)off_out
|
||||
sqe.len = len
|
||||
sqe.splice_flags = flags
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a tee(2) system call.
|
||||
|
||||
Please note that both of the file descriptors must refer to a pipe.
|
||||
See also tee(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.8.
|
||||
*/
|
||||
tee :: proc(ring: ^Ring, user_data: u64, fd_in: linux.Fd, fd_out: linux.Fd, len: u32, flags: linux.IO_Uring_Splice_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .TEE
|
||||
sqe.splice_fd_in = fd_in
|
||||
sqe.fd = fd_out
|
||||
sqe.len = len
|
||||
sqe.splice_flags = flags
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
This command is an alternative to using IORING_REGISTER_FILES_UPDATE which then works in an async fashion, like the rest of the uring commands.
|
||||
|
||||
Note that the array of file descriptors pointed to in addr must remain valid until this operation has completed.
|
||||
|
||||
Available since 5.6.
|
||||
*/
|
||||
files_update :: proc(ring: ^Ring, user_data: u64, fds: []linux.Fd, off: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .FILES_UPDATE
|
||||
sqe.addr = cast(u64)uintptr(raw_data(fds))
|
||||
sqe.len = cast(u32)len(fds)
|
||||
sqe.off = off
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
provide_buffers :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
remove_buffers :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a shutdown(2) system call.
|
||||
|
||||
Available since 5.11.
|
||||
*/
|
||||
shutdown :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, how: linux.Shutdown_How) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .SHUTDOWN
|
||||
sqe.fd = fd
|
||||
sqe.shutdown_how = how
|
||||
sqe.user_data = user_data
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
renameat :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
unlinkat :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
mkdirat :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
symlinkat :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
linkat :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
msg_ring :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
/*
|
||||
Issue the equivalent of a socket(2) system call.
|
||||
|
||||
See also socket(2) for the general description of the related system call.
|
||||
|
||||
Available since 5.19.
|
||||
|
||||
If the file_index field is set to a positive number, the file won't be installed into the normal file
|
||||
table as usual but will be placed into the fixed file table at index file_index - 1.
|
||||
In this case, instead of returning a file descriptor, the result will contain either 0 on success or an error.
|
||||
If the index points to a valid empty slot, the installation is guaranteed to not fail.
|
||||
If there is already a file in the slot, it will be replaced, similar to IORING_OP_FILES_UPDATE.
|
||||
Please note that only uring has access to such files and no other syscall can use them.
|
||||
See IOSQE_FIXED_FILE and IORING_REGISTER_FILES.
|
||||
*/
|
||||
socket :: proc(ring: ^Ring, user_data: u64, domain: linux.Address_Family, socktype: linux.Socket_Type, protocol: linux.Protocol, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .SOCKET
|
||||
sqe.user_data = user_data
|
||||
sqe.fd = cast(linux.Fd)domain
|
||||
sqe.off = cast(u64)socktype
|
||||
sqe.len = cast(u32)protocol
|
||||
sqe.rw_flags = {}
|
||||
sqe.file_index = file_index
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
uring_cmd :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
send_zc :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
sendmsg_zc :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
waitid :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
setxattr :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
getxattr :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
fsetxattr :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
fgetxattr :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
/*
|
||||
Issues the equivalent of the bind(2) system call.
|
||||
|
||||
Available since 6.11.
|
||||
*/
|
||||
bind :: proc(ring: ^Ring, user_data: u64, sock: linux.Fd, addr: ^$T) -> (sqe: linux.IO_Uring_SQE, ok: bool)
|
||||
where
|
||||
T == linux.Sock_Addr_In ||
|
||||
T == linux.Sock_Addr_In6 ||
|
||||
T == linux.Sock_Addr_Un ||
|
||||
T == linux.Sock_Addr_Any
|
||||
{
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .BIND
|
||||
sqe.user_data = user_data
|
||||
sqe.fd = sock
|
||||
sqe.addr = cast(u64)uintptr(addr)
|
||||
sqe.addr2 = size_of(T)
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Issues the equivalent of the listen(2) system call.
|
||||
|
||||
fd must contain the file descriptor of the socket and addr must contain the backlog parameter, i.e. the maximum amount of pending queued connections.
|
||||
|
||||
Available since 6.11.
|
||||
*/
|
||||
listen :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, backlog: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sqe = get_sqe(ring) or_return
|
||||
sqe.opcode = .LISTEN
|
||||
sqe.user_data = user_data
|
||||
sqe.fd = fd
|
||||
sqe.addr = backlog
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
ftruncate :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
read_multishot :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
futex_wait :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
futex_wake :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
futex_waitv :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
fixed_fd_install :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
fixed_file :: proc() {
|
||||
unimplemented()
|
||||
}
|
||||
294
core/sys/linux/uring/uring.odin
Normal file
294
core/sys/linux/uring/uring.odin
Normal file
@@ -0,0 +1,294 @@
|
||||
package uring
|
||||
|
||||
import "core:math"
|
||||
import "core:sync"
|
||||
import "core:sys/linux"
|
||||
|
||||
DEFAULT_THREAD_IDLE_MS :: 1000
|
||||
DEFAULT_ENTRIES :: 32
|
||||
MAX_ENTRIES :: 4096
|
||||
|
||||
Ring :: struct {
|
||||
fd: linux.Fd,
|
||||
sq: Submission_Queue,
|
||||
cq: Completion_Queue,
|
||||
flags: linux.IO_Uring_Setup_Flags,
|
||||
features: linux.IO_Uring_Features,
|
||||
}
|
||||
|
||||
DEFAULT_PARAMS :: linux.IO_Uring_Params {
|
||||
sq_thread_idle = DEFAULT_THREAD_IDLE_MS,
|
||||
}
|
||||
|
||||
// Initialize and setup an uring, `entries` must be a power of 2 between 1 and 4096.
|
||||
init :: proc(ring: ^Ring, params: ^linux.IO_Uring_Params, entries: u32 = DEFAULT_ENTRIES) -> (err: linux.Errno) {
|
||||
assert(entries <= MAX_ENTRIES, "too many entries")
|
||||
assert(entries != 0, "entries must be positive")
|
||||
assert(math.is_power_of_two(int(entries)), "entries must be a power of two")
|
||||
|
||||
fd := linux.io_uring_setup(entries, params) or_return
|
||||
defer if err != nil { linux.close(fd) }
|
||||
|
||||
if .SINGLE_MMAP not_in params.features {
|
||||
// NOTE: Could support this, but currently isn't.
|
||||
err = .ENOSYS
|
||||
return
|
||||
}
|
||||
|
||||
assert(.CQE32 not_in params.flags, "unsupported flag") // NOTE: Could support this by making IO_Uring generic.
|
||||
assert(.SQE128 not_in params.flags, "unsupported flag") // NOTE: Could support this by making IO_Uring generic.
|
||||
|
||||
sq := submission_queue_make(fd, params) or_return
|
||||
|
||||
ring.fd = fd
|
||||
ring.sq = sq
|
||||
ring.cq = completion_queue_make(fd, params, &sq)
|
||||
ring.flags = params.flags
|
||||
ring.features = params.features
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
destroy :: proc(ring: ^Ring) {
|
||||
assert(ring.fd >= 0)
|
||||
submission_queue_destroy(&ring.sq)
|
||||
linux.close(ring.fd)
|
||||
ring.fd = -1
|
||||
}
|
||||
|
||||
// Returns a pointer to a vacant submission queue entry, or nil if the submission queue is full.
|
||||
// NOTE: extra is so you can make sure there is space for related entries, defaults to 1 so
|
||||
// a link timeout op can always be added after another.
|
||||
get_sqe :: proc(ring: ^Ring, extra: int = 1) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) {
|
||||
sq := &ring.sq
|
||||
head: u32 = sync.atomic_load_explicit(sq.head, .Acquire)
|
||||
next := sq.sqe_tail + 1
|
||||
|
||||
if int(next - head) > len(sq.sqes)-extra {
|
||||
sqe = nil
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
|
||||
sqe = &sq.sqes[sq.sqe_tail & sq.mask]
|
||||
sqe^ = {}
|
||||
|
||||
sq.sqe_tail = next
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
free_space :: proc(ring: ^Ring) -> int {
|
||||
sq := &ring.sq
|
||||
head := sync.atomic_load_explicit(sq.head, .Acquire)
|
||||
next := sq.sqe_tail + 1
|
||||
free := len(sq.sqes) - int(next - head)
|
||||
assert(free >= 0)
|
||||
return free
|
||||
}
|
||||
|
||||
// Sync internal state with kernel ring state on the submission queue side.
|
||||
// Returns the number of all pending events in the submission queue.
|
||||
// Rationale is to determine that an enter call is needed.
|
||||
flush_sq :: proc(ring: ^Ring) -> (n_pending: u32) {
|
||||
sq := &ring.sq
|
||||
to_submit := sq.sqe_tail - sq.sqe_head
|
||||
if to_submit != 0 {
|
||||
tail := sq.tail^
|
||||
i: u32 = 0
|
||||
for ; i < to_submit; i += 1 {
|
||||
sq.array[tail & sq.mask] = sq.sqe_head & sq.mask
|
||||
tail += 1
|
||||
sq.sqe_head += 1
|
||||
}
|
||||
sync.atomic_store_explicit(sq.tail, tail, .Release)
|
||||
}
|
||||
n_pending = sq_ready(ring)
|
||||
return
|
||||
}
|
||||
|
||||
// Returns true if we are not using an SQ thread (thus nobody submits but us),
|
||||
// or if IORING_SQ_NEED_WAKEUP is set and the SQ thread must be explicitly awakened.
|
||||
// For the latter case, we set the SQ thread wakeup flag.
|
||||
// Matches the implementation of sq_ring_needs_enter() in liburing.
|
||||
sq_ring_needs_enter :: proc(ring: ^Ring, flags: ^linux.IO_Uring_Enter_Flags) -> bool {
|
||||
assert(flags^ == {})
|
||||
if .SQPOLL not_in ring.flags { return true }
|
||||
if .NEED_WAKEUP in sync.atomic_load_explicit(ring.sq.flags, .Relaxed) {
|
||||
flags^ += {.SQ_WAKEUP}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Submits the submission queue entries acquired via get_sqe().
|
||||
// Returns the number of entries submitted.
|
||||
// Optionally wait for a number of events by setting `wait_nr`, and/or set a maximum wait time by setting `timeout`.
|
||||
submit :: proc(ring: ^Ring, wait_nr: u32 = 0, timeout: ^linux.Time_Spec = nil) -> (n_submitted: u32, err: linux.Errno) {
|
||||
n_submitted = flush_sq(ring)
|
||||
flags: linux.IO_Uring_Enter_Flags
|
||||
if sq_ring_needs_enter(ring, &flags) || wait_nr > 0 {
|
||||
if wait_nr > 0 || .IOPOLL in ring.flags {
|
||||
flags += {.GETEVENTS}
|
||||
}
|
||||
|
||||
flags += {.EXT_ARG}
|
||||
ext: linux.IO_Uring_Getevents_Arg
|
||||
ext.ts = timeout
|
||||
|
||||
n_submitted_: int
|
||||
n_submitted_, err = linux.io_uring_enter2(ring.fd, n_submitted, wait_nr, flags, &ext)
|
||||
assert(n_submitted_ >= 0)
|
||||
n_submitted = u32(n_submitted_)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the number of submission queue entries in the submission queue.
|
||||
sq_ready :: proc(ring: ^Ring) -> u32 {
|
||||
// Always use the shared ring state (i.e. head and not sqe_head) to avoid going out of sync,
|
||||
// see https://github.com/axboe/liburing/issues/92.
|
||||
return ring.sq.sqe_tail - sync.atomic_load_explicit(ring.sq.head, .Acquire)
|
||||
}
|
||||
|
||||
// Returns the number of completion queue entries in the completion queue (yet to consume).
|
||||
cq_ready :: proc(ring: ^Ring) -> (n_ready: u32) {
|
||||
return sync.atomic_load_explicit(ring.cq.tail, .Acquire) - ring.cq.head^
|
||||
}
|
||||
|
||||
// Copies as many CQEs as are ready, and that can fit into the destination `cqes` slice.
|
||||
// If none are available, enters into the kernel to wait for at most `wait_nr` CQEs.
|
||||
// Returns the number of CQEs copied, advancing the CQ ring.
|
||||
// Provides all the wait/peek methods found in liburing, but with batching and a single method.
|
||||
// TODO: allow for timeout.
|
||||
copy_cqes :: proc(ring: ^Ring, cqes: []linux.IO_Uring_CQE, wait_nr: u32) -> (n_copied: u32, err: linux.Errno) {
|
||||
n_copied = copy_cqes_ready(ring, cqes)
|
||||
if n_copied > 0 { return }
|
||||
if wait_nr > 0 || cq_ring_needs_flush(ring) {
|
||||
_ = linux.io_uring_enter(ring.fd, 0, wait_nr, {.GETEVENTS}, nil) or_return
|
||||
n_copied = copy_cqes_ready(ring, cqes)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
copy_cqes_ready :: proc(ring: ^Ring, cqes: []linux.IO_Uring_CQE) -> (n_copied: u32) {
|
||||
n_ready := cq_ready(ring)
|
||||
n_copied = min(u32(len(cqes)), n_ready)
|
||||
head := ring.cq.head^
|
||||
tail := head + n_copied
|
||||
shift := u32(.CQE32 in ring.flags)
|
||||
|
||||
i := 0
|
||||
for head != tail {
|
||||
cqes[i] = ring.cq.cqes[(head & ring.cq.mask) << shift]
|
||||
head += 1
|
||||
i += 1
|
||||
}
|
||||
cq_advance(ring, n_copied)
|
||||
return
|
||||
}
|
||||
|
||||
cq_ring_needs_flush :: proc(ring: ^Ring) -> bool {
|
||||
return .CQ_OVERFLOW in sync.atomic_load_explicit(ring.sq.flags, .Relaxed)
|
||||
}
|
||||
|
||||
// For advanced use cases only that implement custom completion queue methods.
|
||||
// If you use copy_cqes() or copy_cqe() you must not call cqe_seen() or cq_advance().
|
||||
// Must be called exactly once after a zero-copy CQE has been processed by your application.
|
||||
// Not idempotent, calling more than once will result in other CQEs being lost.
|
||||
// Matches the implementation of cqe_seen() in liburing.
|
||||
cqe_seen :: proc(ring: ^Ring) {
|
||||
cq_advance(ring, 1)
|
||||
}
|
||||
|
||||
// For advanced use cases only that implement custom completion queue methods.
|
||||
// Matches the implementation of cq_advance() in liburing.
|
||||
cq_advance :: proc(ring: ^Ring, count: u32) {
|
||||
if count == 0 { return }
|
||||
sync.atomic_store_explicit(ring.cq.head, ring.cq.head^ + count, .Release)
|
||||
}
|
||||
|
||||
Submission_Queue :: struct {
|
||||
head: ^u32,
|
||||
tail: ^u32,
|
||||
mask: u32,
|
||||
flags: ^linux.IO_Uring_Submission_Queue_Flags,
|
||||
dropped: ^u32,
|
||||
array: []u32,
|
||||
sqes: []linux.IO_Uring_SQE,
|
||||
mmap: []u8,
|
||||
mmap_sqes: []u8,
|
||||
|
||||
// We use `sqe_head` and `sqe_tail` in the same way as liburing:
|
||||
// We increment `sqe_tail` (but not `tail`) for each call to `get_sqe()`.
|
||||
// We then set `tail` to `sqe_tail` once, only when these events are actually submitted.
|
||||
// This allows us to amortize the cost of the @atomicStore to `tail` across multiple SQEs.
|
||||
sqe_head: u32,
|
||||
sqe_tail: u32,
|
||||
}
|
||||
|
||||
submission_queue_make :: proc(fd: linux.Fd, params: ^linux.IO_Uring_Params) -> (sq: Submission_Queue, err: linux.Errno) {
|
||||
assert(fd >= 0, "uninitialized queue fd")
|
||||
assert(.SINGLE_MMAP in params.features, "unsupported feature") // NOTE: Could support this, but currently isn't.
|
||||
|
||||
sq_size := params.sq_off.array + params.sq_entries * size_of(u32)
|
||||
cq_size := params.cq_off.cqes + params.cq_entries * size_of(linux.IO_Uring_CQE)
|
||||
size := max(sq_size, cq_size)
|
||||
|
||||
// PERF: .POPULATE commits all pages right away, is that desired?
|
||||
|
||||
cqe_map := cast([^]byte)(linux.mmap(0, uint(size), {.READ, .WRITE}, {.SHARED, .POPULATE}, fd, linux.IORING_OFF_SQ_RING) or_return)
|
||||
defer if err != nil { linux.munmap(cqe_map, uint(size)) }
|
||||
|
||||
size_sqes := params.sq_entries * size_of(linux.IO_Uring_SQE)
|
||||
sqe_map := cast([^]byte)(linux.mmap(0, uint(size_sqes), {.READ, .WRITE}, {.SHARED, .POPULATE}, fd, linux.IORING_OFF_SQES) or_return)
|
||||
|
||||
array := cast([^]u32)cqe_map[params.sq_off.array:]
|
||||
sqes := cast([^]linux.IO_Uring_SQE)sqe_map
|
||||
|
||||
sq.head = cast(^u32)&cqe_map[params.sq_off.head]
|
||||
sq.tail = cast(^u32)&cqe_map[params.sq_off.tail]
|
||||
sq.mask = (cast(^u32)&cqe_map[params.sq_off.ring_mask])^
|
||||
sq.flags = cast(^linux.IO_Uring_Submission_Queue_Flags)&cqe_map[params.sq_off.flags]
|
||||
sq.dropped = cast(^u32)&cqe_map[params.sq_off.dropped]
|
||||
sq.array = array[:params.sq_entries]
|
||||
sq.sqes = sqes[:params.sq_entries]
|
||||
sq.mmap = cqe_map[:size]
|
||||
sq.mmap_sqes = sqe_map[:size_sqes]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
submission_queue_destroy :: proc(sq: ^Submission_Queue) -> (err: linux.Errno) {
|
||||
err = linux.munmap(raw_data(sq.mmap), uint(len(sq.mmap)))
|
||||
err2 := linux.munmap(raw_data(sq.mmap_sqes), uint(len(sq.mmap_sqes)))
|
||||
if err == nil { err = err2 }
|
||||
return
|
||||
}
|
||||
|
||||
Completion_Queue :: struct {
|
||||
head: ^u32,
|
||||
tail: ^u32,
|
||||
mask: u32,
|
||||
overflow: ^u32,
|
||||
cqes: []linux.IO_Uring_CQE,
|
||||
}
|
||||
|
||||
completion_queue_make :: proc(fd: linux.Fd, params: ^linux.IO_Uring_Params, sq: ^Submission_Queue) -> Completion_Queue {
|
||||
assert(fd >= 0, "uninitialized queue fd")
|
||||
assert(.SINGLE_MMAP in params.features, "required feature SINGLE_MMAP not supported")
|
||||
|
||||
mmap := sq.mmap
|
||||
cqes := cast([^]linux.IO_Uring_CQE)&mmap[params.cq_off.cqes]
|
||||
|
||||
return(
|
||||
{
|
||||
head = cast(^u32)&mmap[params.cq_off.head],
|
||||
tail = cast(^u32)&mmap[params.cq_off.tail],
|
||||
mask = (cast(^u32)&mmap[params.cq_off.ring_mask])^,
|
||||
overflow = cast(^u32)&mmap[params.cq_off.overflow],
|
||||
cqes = cqes[:params.cq_entries],
|
||||
} \
|
||||
)
|
||||
}
|
||||
@@ -70,7 +70,7 @@ foreign lib {
|
||||
|
||||
[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]]
|
||||
*/
|
||||
openat :: proc(fd: FD, path: cstring, flags: O_Flags, mode: mode_t = {}) -> FD ---
|
||||
openat :: proc(fd: FD, path: cstring, flags: O_Flags, #c_vararg mode: ..mode_t) -> FD ---
|
||||
}
|
||||
|
||||
FCNTL_Cmd :: enum c.int {
|
||||
|
||||
@@ -30,6 +30,16 @@ EV_RXCHAR :: DWORD(0x0001)
|
||||
EV_RXFLAG :: DWORD(0x0002)
|
||||
EV_TXEMPTY :: DWORD(0x0004)
|
||||
|
||||
WAITORTIMERCALLBACK :: #type proc "system" (lpParameter: PVOID, TimerOrWaitFired: BOOLEAN)
|
||||
|
||||
WT_EXECUTEDEFAULT :: 0x00000000
|
||||
WT_EXECUTEINIOTHREAD :: 0x00000001
|
||||
WT_EXECUTEINPERSISTENTTHREAD :: 0x00000080
|
||||
WT_EXECUTEINWAITTHREAD :: 0x00000004
|
||||
WT_EXECUTELONGFUNCTION :: 0x00000010
|
||||
WT_EXECUTEONLYONCE :: 0x00000008
|
||||
WT_TRANSFER_IMPERSONATION :: 0x00000100
|
||||
|
||||
@(default_calling_convention="system")
|
||||
foreign kernel32 {
|
||||
OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed
|
||||
@@ -567,7 +577,7 @@ foreign kernel32 {
|
||||
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatusex)
|
||||
GetQueuedCompletionStatusEx :: proc(CompletionPort: HANDLE, lpCompletionPortEntries: ^OVERLAPPED_ENTRY, ulCount: c_ulong, ulNumEntriesRemoved: ^c_ulong, dwMilliseconds: DWORD, fAlertable: BOOL) -> BOOL ---
|
||||
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-postqueuedcompletionstatus)
|
||||
PostQueuedCompletionStatus :: proc(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: c_ulong, lpOverlapped: ^OVERLAPPED) -> BOOL ---
|
||||
PostQueuedCompletionStatus :: proc(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: ULONG_PTR, lpOverlapped: ^OVERLAPPED) -> BOOL ---
|
||||
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-gethandleinformation)
|
||||
GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL ---
|
||||
|
||||
@@ -575,6 +585,17 @@ foreign kernel32 {
|
||||
RtlNtStatusToDosError :: proc(status: NTSTATUS) -> ULONG ---
|
||||
|
||||
GetSystemPowerStatus :: proc(lpSystemPowerStatus: ^SYSTEM_POWER_STATUS) -> BOOL ---
|
||||
|
||||
RegisterWaitForSingleObject :: proc(
|
||||
phNewWaitObject: PHANDLE,
|
||||
hObject: HANDLE,
|
||||
Callback: WAITORTIMERCALLBACK,
|
||||
Context: PVOID,
|
||||
dwMilliseconds: ULONG,
|
||||
dwFlags: ULONG,
|
||||
) -> BOOL ---
|
||||
|
||||
UnregisterWaitEx :: proc(WaitHandle: HANDLE, CompletionEvent: HANDLE) -> BOOL ---
|
||||
}
|
||||
|
||||
DEBUG_PROCESS :: 0x00000001
|
||||
|
||||
40
core/sys/windows/mswsock.odin
Normal file
40
core/sys/windows/mswsock.odin
Normal file
@@ -0,0 +1,40 @@
|
||||
#+build windows
|
||||
package sys_windows
|
||||
|
||||
foreign import mswsock "system:mswsock.lib"
|
||||
|
||||
foreign mswsock {
|
||||
TransmitFile :: proc(
|
||||
hSocket: SOCKET,
|
||||
hFile: HANDLE,
|
||||
nNumberOfBytesToWrite: DWORD,
|
||||
nNumberOfBytesPerSend: DWORD,
|
||||
lpOverlapped: LPOVERLAPPED,
|
||||
lpTransmitBuffers: rawptr,
|
||||
dwReserved: DWORD,
|
||||
) -> BOOL ---
|
||||
|
||||
AcceptEx :: proc(
|
||||
sListenSocket: SOCKET,
|
||||
sAcceptSocket: SOCKET,
|
||||
lpOutputBuffer: PVOID,
|
||||
dwReceiveDataLength: DWORD,
|
||||
dwLocalAddressLength: DWORD,
|
||||
dwRemoteAddressLength: DWORD,
|
||||
lpdwBytesReceived: LPDWORD,
|
||||
lpOverlapped: LPOVERLAPPED,
|
||||
) -> BOOL ---
|
||||
|
||||
GetAcceptExSockaddrs :: proc(
|
||||
lpOutputBuffer: PVOID,
|
||||
dwReceiveDataLength: DWORD,
|
||||
dwLocalAddressLength: DWORD,
|
||||
dwRemoteAddressLength: DWORD,
|
||||
LocalSockaddr: ^^sockaddr,
|
||||
LocalSockaddrLength: LPINT,
|
||||
RemoteSockaddr: ^^sockaddr,
|
||||
RemoteSockaddrLength: LPINT,
|
||||
) ---
|
||||
}
|
||||
|
||||
SO_UPDATE_CONNECT_CONTEXT :: 0x7010
|
||||
@@ -36,6 +36,20 @@ foreign ntdll_lib {
|
||||
QueryFlags: ULONG,
|
||||
FileName : PUNICODE_STRING,
|
||||
) -> NTSTATUS ---
|
||||
|
||||
NtCreateFile :: proc(
|
||||
FileHandle: PHANDLE,
|
||||
DesiredAccess: ACCESS_MASK,
|
||||
ObjectAttributes: POBJECT_ATTRIBUTES,
|
||||
IoStatusBlock: PIO_STATUS_BLOCK,
|
||||
AllocationSize: PLARGE_INTEGER,
|
||||
FileAttributes: ULONG,
|
||||
ShareAccess: ULONG,
|
||||
CreateDisposition: ULONG,
|
||||
CreateOptions: ULONG,
|
||||
EaBuffer: PVOID,
|
||||
EaLength: ULONG,
|
||||
) -> NTSTATUS ---
|
||||
}
|
||||
|
||||
|
||||
@@ -256,4 +270,13 @@ RTL_DRIVE_LETTER_CURDIR :: struct {
|
||||
LIST_ENTRY :: struct {
|
||||
Flink: ^LIST_ENTRY,
|
||||
Blink: ^LIST_ENTRY,
|
||||
}
|
||||
}
|
||||
|
||||
FILE_SUPERSEDE :: 0x00000000
|
||||
FILE_OPEN :: 0x00000001
|
||||
FILE_CREATE :: 0x00000002
|
||||
FILE_OPEN_IF :: 0x00000003
|
||||
FILE_OVERWRITE :: 0x00000004
|
||||
FILE_OVERWRITE_IF :: 0x00000005
|
||||
|
||||
FILE_NON_DIRECTORY_FILE :: 0x00000040
|
||||
|
||||
@@ -3139,6 +3139,7 @@ OBJECT_ATTRIBUTES :: struct {
|
||||
SecurityDescriptor: rawptr,
|
||||
SecurityQualityOfService: rawptr,
|
||||
}
|
||||
POBJECT_ATTRIBUTES :: ^OBJECT_ATTRIBUTES
|
||||
|
||||
PUNICODE_STRING :: ^UNICODE_STRING
|
||||
UNICODE_STRING :: struct {
|
||||
@@ -3150,9 +3151,14 @@ UNICODE_STRING :: struct {
|
||||
OVERLAPPED :: struct {
|
||||
Internal: ^c_ulong,
|
||||
InternalHigh: ^c_ulong,
|
||||
Offset: DWORD,
|
||||
OffsetHigh: DWORD,
|
||||
hEvent: HANDLE,
|
||||
using _: struct #raw_union {
|
||||
using _: struct {
|
||||
Offset: DWORD,
|
||||
OffsetHigh: DWORD,
|
||||
},
|
||||
OffsetFull: u64, // Convenience field to set Offset and OffsetHigh with one value.
|
||||
},
|
||||
hEvent: HANDLE,
|
||||
}
|
||||
|
||||
OVERLAPPED_ENTRY :: struct {
|
||||
|
||||
@@ -248,6 +248,13 @@ E_HANDLE :: 0x80070006 // Handle that is not valid
|
||||
E_OUTOFMEMORY :: 0x8007000E // Failed to allocate necessary memory
|
||||
E_INVALIDARG :: 0x80070057 // One or more arguments are not valid
|
||||
|
||||
SEC_E_INCOMPLETE_MESSAGE :: 0x80090318
|
||||
|
||||
SEC_I_INCOMPLETE_CREDENTIALS :: 0x00090320
|
||||
SEC_I_CONTINUE_NEEDED :: 0x00090312
|
||||
SEC_I_CONTEXT_EXPIRED :: 0x00090317
|
||||
SEC_I_RENEGOTIATE :: 0x00090321
|
||||
|
||||
// Severity values
|
||||
SEVERITY :: enum DWORD {
|
||||
SUCCESS = 0,
|
||||
|
||||
@@ -38,7 +38,7 @@ WSANETWORKEVENTS :: struct {
|
||||
|
||||
WSAID_ACCEPTEX :: GUID{0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}}
|
||||
WSAID_GETACCEPTEXSOCKADDRS :: GUID{0xb5367df2, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}}
|
||||
WSAID_CONNECTX :: GUID{0x25a207b9, 0xddf3, 0x4660, {0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}}
|
||||
WSAID_CONNECTEX :: GUID{0x25a207b9, 0xddf3, 0x4660, {0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}}
|
||||
|
||||
SIO_GET_EXTENSION_FUNCTION_POINTER :: IOC_INOUT | IOC_WS2 | 6
|
||||
SIO_UDP_CONNRESET :: IOC_IN | IOC_VENDOR | 12
|
||||
@@ -129,7 +129,7 @@ foreign ws2_32 {
|
||||
dwBufferCount: DWORD,
|
||||
lpNumberOfBytesSent: LPDWORD,
|
||||
dwFlags: DWORD,
|
||||
lpTo: ^SOCKADDR_STORAGE_LH,
|
||||
lpTo: ^sockaddr,
|
||||
iToLen: c_int,
|
||||
lpOverlapped: LPWSAOVERLAPPED,
|
||||
lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE,
|
||||
@@ -151,8 +151,8 @@ foreign ws2_32 {
|
||||
dwBufferCount: DWORD,
|
||||
lpNumberOfBytesRecvd: LPDWORD,
|
||||
lpFlags: LPDWORD,
|
||||
lpFrom: ^SOCKADDR_STORAGE_LH,
|
||||
lpFromlen: ^c_int,
|
||||
lpFrom: ^sockaddr,
|
||||
lpFromlen: LPINT,
|
||||
lpOverlapped: LPWSAOVERLAPPED,
|
||||
lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE,
|
||||
) -> c_int ---
|
||||
|
||||
@@ -5,7 +5,10 @@ import "base:runtime"
|
||||
import "core:mem"
|
||||
import "base:intrinsics"
|
||||
|
||||
_ :: intrinsics
|
||||
@(private)
|
||||
unall :: intrinsics.unaligned_load
|
||||
@(private)
|
||||
unals :: intrinsics.unaligned_store
|
||||
|
||||
/*
|
||||
Value, specifying whether `core:thread` functionality is available on the
|
||||
@@ -347,7 +350,9 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex
|
||||
thread_proc :: proc(t: ^Thread) {
|
||||
fn := cast(proc(T))t.data
|
||||
assert(t.user_index >= 1)
|
||||
data := (^T)(&t.user_args[0])^
|
||||
|
||||
data := unall((^T)(&t.user_args))
|
||||
|
||||
fn(data)
|
||||
}
|
||||
if t = create(thread_proc, priority); t == nil {
|
||||
@@ -356,9 +361,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex
|
||||
t.data = rawptr(fn)
|
||||
t.user_index = 1
|
||||
|
||||
data := data
|
||||
|
||||
mem.copy(&t.user_args[0], &data, size_of(T))
|
||||
unals((^T)(&t.user_args), data)
|
||||
|
||||
if self_cleanup {
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
@@ -393,9 +396,10 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2),
|
||||
fn := cast(proc(T1, T2))t.data
|
||||
assert(t.user_index >= 2)
|
||||
|
||||
user_args := mem.slice_to_bytes(t.user_args[:])
|
||||
arg1 := (^T1)(raw_data(user_args))^
|
||||
arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
|
||||
ptr := uintptr(&t.user_args)
|
||||
|
||||
arg1 := unall((^T1)(rawptr(ptr)))
|
||||
arg2 := unall((^T2)(rawptr(ptr + size_of(T1))))
|
||||
|
||||
fn(arg1, arg2)
|
||||
}
|
||||
@@ -405,11 +409,10 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2),
|
||||
t.data = rawptr(fn)
|
||||
t.user_index = 2
|
||||
|
||||
arg1, arg2 := arg1, arg2
|
||||
user_args := mem.slice_to_bytes(t.user_args[:])
|
||||
ptr := uintptr(&t.user_args)
|
||||
|
||||
n := copy(user_args, mem.ptr_to_bytes(&arg1))
|
||||
_ = copy(user_args[n:], mem.ptr_to_bytes(&arg2))
|
||||
unals((^T1)(rawptr(ptr)), arg1)
|
||||
unals((^T2)(rawptr(ptr + size_of(T1))), arg2)
|
||||
|
||||
if self_cleanup {
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
@@ -444,10 +447,11 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr
|
||||
fn := cast(proc(T1, T2, T3))t.data
|
||||
assert(t.user_index >= 3)
|
||||
|
||||
user_args := mem.slice_to_bytes(t.user_args[:])
|
||||
arg1 := (^T1)(raw_data(user_args))^
|
||||
arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^
|
||||
arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^
|
||||
ptr := uintptr(&t.user_args)
|
||||
|
||||
arg1 := unall((^T1)(rawptr(ptr)))
|
||||
arg2 := unall((^T2)(rawptr(ptr + size_of(T1))))
|
||||
arg3 := unall((^T3)(rawptr(ptr + size_of(T1) + size_of(T2))))
|
||||
|
||||
fn(arg1, arg2, arg3)
|
||||
}
|
||||
@@ -457,12 +461,11 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr
|
||||
t.data = rawptr(fn)
|
||||
t.user_index = 3
|
||||
|
||||
arg1, arg2, arg3 := arg1, arg2, arg3
|
||||
user_args := mem.slice_to_bytes(t.user_args[:])
|
||||
ptr := uintptr(&t.user_args)
|
||||
|
||||
n := copy(user_args, mem.ptr_to_bytes(&arg1))
|
||||
n += copy(user_args[n:], mem.ptr_to_bytes(&arg2))
|
||||
_ = copy(user_args[n:], mem.ptr_to_bytes(&arg3))
|
||||
unals((^T1)(rawptr(ptr)), arg1)
|
||||
unals((^T2)(rawptr(ptr + size_of(T1))), arg2)
|
||||
unals((^T3)(rawptr(ptr + size_of(T1) + size_of(T2))), arg3)
|
||||
|
||||
if self_cleanup {
|
||||
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
package all
|
||||
|
||||
@(require) import "core:sys/linux"
|
||||
@(require) import "vendor:x11/xlib"
|
||||
@(require) import "core:sys/linux/uring"
|
||||
@(require) import "vendor:x11/xlib"
|
||||
|
||||
@@ -17,6 +17,7 @@ package all
|
||||
|
||||
@(require) import "core:container/avl"
|
||||
@(require) import "core:container/bit_array"
|
||||
@(require) import "core:container/pool"
|
||||
@(require) import "core:container/priority_queue"
|
||||
@(require) import "core:container/queue"
|
||||
@(require) import "core:container/small_array"
|
||||
@@ -104,6 +105,8 @@ package all
|
||||
@(require) import "core:mem/tlsf"
|
||||
@(require) import "core:mem/virtual"
|
||||
|
||||
@(require) import "core:nbio"
|
||||
|
||||
@(require) import "core:odin/ast"
|
||||
@(require) import doc_format "core:odin/doc-format"
|
||||
@(require) import "core:odin/parser"
|
||||
@@ -156,4 +159,4 @@ package all
|
||||
@(require) import "core:unicode/utf8/utf8string"
|
||||
@(require) import "core:unicode/utf16"
|
||||
|
||||
main :: proc() {}
|
||||
main :: proc() {}
|
||||
|
||||
@@ -4,22 +4,15 @@ import rb "core:container/rbtree"
|
||||
import "core:math/rand"
|
||||
import "core:testing"
|
||||
import "base:intrinsics"
|
||||
import "core:mem"
|
||||
import "core:slice"
|
||||
import "core:log"
|
||||
|
||||
test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
track: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track, context.allocator)
|
||||
track.bad_free_callback = mem.tracking_allocator_bad_free_callback_add_to_array
|
||||
defer mem.tracking_allocator_destroy(&track)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
|
||||
log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed)
|
||||
tree: rb.Tree(Key, Value)
|
||||
rb.init(&tree)
|
||||
|
||||
testing.expect(t, rb.len(&tree) == 0, "empty: len should be 0")
|
||||
testing.expect(t, rb.len(tree) == 0, "empty: len should be 0")
|
||||
testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil")
|
||||
testing.expect(t, rb.last(&tree) == nil, "empty: last should be nil")
|
||||
iter := rb.iterator(&tree, .Forward)
|
||||
@@ -48,7 +41,7 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
}
|
||||
|
||||
entry_count := len(inserted_map)
|
||||
testing.expect(t, rb.len(&tree) == entry_count, "insert: len after")
|
||||
testing.expect(t, rb.len(tree) == entry_count, "insert: len after")
|
||||
validate_rbtree(t, &tree)
|
||||
|
||||
first := rb.first(&tree)
|
||||
@@ -58,8 +51,8 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
|
||||
// Ensure that all entries can be found.
|
||||
for k, v in inserted_map {
|
||||
testing.expect(t, v == rb.find(&tree, k), "Find(): Node")
|
||||
testing.expect(t, k == v.key, "Find(): Node key")
|
||||
testing.expect(t, v == rb.find(tree, k), "Find(): Node")
|
||||
testing.expect(t, k == v.key, "Find(): Node key")
|
||||
}
|
||||
|
||||
// Test the forward/backward iterators.
|
||||
@@ -97,17 +90,17 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
(^int)(user_data)^ -= 1
|
||||
}
|
||||
for k, i in inserted_keys {
|
||||
node := rb.find(&tree, k)
|
||||
node := rb.find(tree, k)
|
||||
testing.expect(t, node != nil, "remove: find (pre)")
|
||||
|
||||
ok := rb.remove(&tree, k)
|
||||
testing.expect(t, ok, "remove: succeeds")
|
||||
testing.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)")
|
||||
testing.expect(t, entry_count - (i + 1) == rb.len(tree), "remove: len (post)")
|
||||
validate_rbtree(t, &tree)
|
||||
|
||||
testing.expect(t, nil == rb.find(&tree, k), "remove: find (post")
|
||||
testing.expect(t, nil == rb.find(tree, k), "remove: find (post")
|
||||
}
|
||||
testing.expect(t, rb.len(&tree) == 0, "remove: len should be 0")
|
||||
testing.expect(t, rb.len(tree) == 0, "remove: len should be 0")
|
||||
testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)
|
||||
testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil")
|
||||
testing.expect(t, rb.last(&tree) == nil, "remove: last should be nil")
|
||||
@@ -129,28 +122,25 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||
ok = rb.iterator_remove(&iter)
|
||||
testing.expect(t, !ok, "iterator/remove: redundant removes should fail")
|
||||
|
||||
testing.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone")
|
||||
testing.expect(t, rb.find(tree, k) == nil, "iterator/remove: node should be gone")
|
||||
testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil")
|
||||
|
||||
// Ensure that iterator_next still works.
|
||||
node, ok = rb.iterator_next(&iter)
|
||||
testing.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false")
|
||||
testing.expect(t, ok == (rb.len(tree) > 0), "iterator/remove: next should return false")
|
||||
testing.expect(t, node == rb.first(&tree), "iterator/remove: next should return first")
|
||||
|
||||
validate_rbtree(t, &tree)
|
||||
}
|
||||
testing.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1")
|
||||
testing.expect(t, rb.len(tree) == entry_count - 1, "iterator/remove: len should drop by 1")
|
||||
|
||||
rb.destroy(&tree)
|
||||
testing.expect(t, rb.len(&tree) == 0, "destroy: len should be 0")
|
||||
testing.expect(t, rb.len(tree) == 0, "destroy: len should be 0")
|
||||
testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)
|
||||
|
||||
// print_tree_node(tree._root)
|
||||
delete(inserted_map)
|
||||
delete(inserted_keys)
|
||||
testing.expectf(t, len(track.allocation_map) == 0, "Expected 0 leaks, have %v", len(track.allocation_map))
|
||||
testing.expectf(t, len(track.bad_free_array) == 0, "Expected 0 bad frees, have %v", len(track.bad_free_array))
|
||||
return
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
||||
100
tests/core/nbio/fs.odin
Normal file
100
tests/core/nbio/fs.odin
Normal file
@@ -0,0 +1,100 @@
|
||||
package tests_nbio
|
||||
|
||||
import "core:nbio"
|
||||
import "core:testing"
|
||||
import "core:time"
|
||||
import os "core:os/os2"
|
||||
|
||||
@(test)
|
||||
close_invalid_handle :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
nbio.close(max(nbio.Handle))
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
write_read_close :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
@static content := [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
||||
@static result: [20]byte
|
||||
|
||||
FILENAME :: "test_write_read_close"
|
||||
|
||||
nbio.open_poly(FILENAME, t, on_open, mode={.Read, .Write, .Create, .Trunc})
|
||||
|
||||
on_open :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.open.err, nil)
|
||||
|
||||
nbio.write_poly(op.open.handle, 0, content[:], t, on_write)
|
||||
}
|
||||
|
||||
on_write :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.write.err, nil)
|
||||
ev(t, op.write.written, len(content))
|
||||
|
||||
nbio.read_poly(op.write.handle, 0, result[:], t, on_read, all=true)
|
||||
}
|
||||
|
||||
on_read :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.read.err, nil)
|
||||
ev(t, op.read.read, len(result))
|
||||
ev(t, result, content)
|
||||
|
||||
nbio.close_poly(op.read.handle, t, on_close)
|
||||
}
|
||||
|
||||
on_close :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.close.err, nil)
|
||||
os.remove(FILENAME)
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
read_empty_file :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
FILENAME :: "test_read_empty_file"
|
||||
|
||||
handle, err := nbio.open_sync(FILENAME, mode={.Read, .Write, .Create, .Trunc})
|
||||
ev(t, err, nil)
|
||||
|
||||
buf: [128]byte
|
||||
nbio.read_poly(handle, 0, buf[:], t, proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.read.err, nbio.FS_Error.EOF)
|
||||
ev(t, op.read.read, 0)
|
||||
|
||||
nbio.close_poly(op.read.handle, t, proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.close.err, nil)
|
||||
os.remove(FILENAME)
|
||||
})
|
||||
})
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
read_entire_file :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
nbio.read_entire_file(#file, t, on_read)
|
||||
|
||||
on_read :: proc(t: rawptr, data: []byte, err: nbio.Read_Entire_File_Error) {
|
||||
t := (^testing.T)(t)
|
||||
ev(t, err.value, nil)
|
||||
ev(t, string(data), #load(#file, string))
|
||||
delete(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
258
tests/core/nbio/nbio.odin
Normal file
258
tests/core/nbio/nbio.odin
Normal file
@@ -0,0 +1,258 @@
|
||||
package tests_nbio
|
||||
|
||||
import "core:log"
|
||||
import "core:nbio"
|
||||
import "core:testing"
|
||||
import "core:thread"
|
||||
import "core:time"
|
||||
import os "core:os/os2"
|
||||
|
||||
ev :: testing.expect_value
|
||||
e :: testing.expect
|
||||
|
||||
@(deferred_in=event_loop_guard_exit)
|
||||
event_loop_guard :: proc(t: ^testing.T) -> bool {
|
||||
err := nbio.acquire_thread_event_loop()
|
||||
if err == .Unsupported || !nbio.FULLY_SUPPORTED {
|
||||
log.warn("nbio unsupported, skipping")
|
||||
return false
|
||||
}
|
||||
|
||||
ev(t, err, nil)
|
||||
return true
|
||||
}
|
||||
|
||||
event_loop_guard_exit :: proc(t: ^testing.T) {
|
||||
ev(t, nbio.run(), nil) // Could have some things to clean up from a `defer` in the test.
|
||||
nbio.release_thread_event_loop()
|
||||
}
|
||||
|
||||
// Tests that all poly variants are correctly passing through arguments, and that
|
||||
// all procs eventually get their callback called.
|
||||
//
|
||||
// This is important because the poly procs are only checked when they are called,
|
||||
// So this will also catch any typos in their implementations.
|
||||
@(test)
|
||||
all_poly_work :: proc(tt: ^testing.T) {
|
||||
if event_loop_guard(tt) {
|
||||
testing.set_fail_timeout(tt, time.Minute)
|
||||
|
||||
@static t: ^testing.T
|
||||
t = tt
|
||||
|
||||
@static n: int
|
||||
n = 0
|
||||
NUM_TESTS :: 39
|
||||
|
||||
UDP_SOCKET :: max(nbio.UDP_Socket)
|
||||
TCP_SOCKET :: max(nbio.TCP_Socket)
|
||||
|
||||
tmp, terr := os.create_temp_file("", "tests_nbio_poly*", {.Non_Blocking})
|
||||
ev(t, terr, nil)
|
||||
defer os.close(tmp)
|
||||
|
||||
HANDLE, aerr := nbio.associate_handle(os.fd(tmp))
|
||||
ev(t, aerr, nil)
|
||||
|
||||
_buf: [1]byte
|
||||
buf := _buf[:]
|
||||
|
||||
one :: proc(op: ^nbio.Operation, one: int) {
|
||||
n += 1
|
||||
ev(t, one, 1)
|
||||
}
|
||||
|
||||
two :: proc(op: ^nbio.Operation, one: int, two: int) {
|
||||
n += 1
|
||||
ev(t, one, 1)
|
||||
ev(t, two, 2)
|
||||
}
|
||||
|
||||
three :: proc(op: ^nbio.Operation, one: int, two: int, three: int) {
|
||||
n += 1
|
||||
ev(t, one, 1)
|
||||
ev(t, two, 2)
|
||||
ev(t, three, 3)
|
||||
}
|
||||
|
||||
nbio.accept_poly(TCP_SOCKET, 1, one)
|
||||
nbio.accept_poly2(TCP_SOCKET, 1, 2, two)
|
||||
nbio.accept_poly3(TCP_SOCKET, 1, 2, 3, three)
|
||||
|
||||
nbio.close_poly(max(nbio.Handle), 1, one)
|
||||
nbio.close_poly2(max(nbio.Handle), 1, 2, two)
|
||||
nbio.close_poly3(max(nbio.Handle), 1, 2, 3, three)
|
||||
|
||||
nbio.dial_poly({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, one)
|
||||
nbio.dial_poly2({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, 2, two)
|
||||
nbio.dial_poly3({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, 2, 3, three)
|
||||
|
||||
nbio.recv_poly(TCP_SOCKET, {buf}, 1, one)
|
||||
nbio.recv_poly2(TCP_SOCKET, {buf}, 1, 2, two)
|
||||
nbio.recv_poly3(TCP_SOCKET, {buf}, 1, 2, 3, three)
|
||||
|
||||
nbio.send_poly(TCP_SOCKET, {buf}, 1, one)
|
||||
nbio.send_poly2(TCP_SOCKET, {buf}, 1, 2, two)
|
||||
nbio.send_poly3(TCP_SOCKET, {buf}, 1, 2, 3, three)
|
||||
|
||||
nbio.sendfile_poly(TCP_SOCKET, HANDLE, 1, one)
|
||||
nbio.sendfile_poly2(TCP_SOCKET, HANDLE, 1, 2, two)
|
||||
nbio.sendfile_poly3(TCP_SOCKET, HANDLE, 1, 2, 3, three)
|
||||
|
||||
nbio.read_poly(HANDLE, 0, buf, 1, one)
|
||||
nbio.read_poly2(HANDLE, 0, buf, 1, 2, two)
|
||||
nbio.read_poly3(HANDLE, 0, buf, 1, 2, 3, three)
|
||||
|
||||
nbio.write_poly(HANDLE, 0, buf, 1, one)
|
||||
nbio.write_poly2(HANDLE, 0, buf, 1, 2, two)
|
||||
nbio.write_poly3(HANDLE, 0, buf, 1, 2, 3, three)
|
||||
|
||||
nbio.next_tick_poly(1, one)
|
||||
nbio.next_tick_poly2(1, 2, two)
|
||||
nbio.next_tick_poly3(1, 2, 3, three)
|
||||
|
||||
nbio.timeout_poly(1, 1, one)
|
||||
nbio.timeout_poly2(1, 1, 2, two)
|
||||
nbio.timeout_poly3(1, 1, 2, 3, three)
|
||||
|
||||
nbio.poll_poly(TCP_SOCKET, .Receive, 1, one)
|
||||
nbio.poll_poly2(TCP_SOCKET, .Receive, 1, 2, two)
|
||||
nbio.poll_poly3(TCP_SOCKET, .Receive, 1, 2, 3, three)
|
||||
|
||||
nbio.open_poly("", 1, one)
|
||||
nbio.open_poly2("", 1, 2, two)
|
||||
nbio.open_poly3("", 1, 2, 3, three)
|
||||
|
||||
nbio.stat_poly(HANDLE, 1, one)
|
||||
nbio.stat_poly2(HANDLE, 1, 2, two)
|
||||
nbio.stat_poly3(HANDLE, 1, 2, 3, three)
|
||||
|
||||
ev(t, n, 0) // Test that no callbacks are ran before the loop is ticked.
|
||||
ev(t, nbio.run(), nil)
|
||||
ev(t, n, NUM_TESTS) // Test that all callbacks have ran.
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
two_ops_at_the_same_time :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
server, err := nbio.create_udp_socket(.IP4)
|
||||
ev(t, err, nil)
|
||||
defer nbio.close(server)
|
||||
|
||||
berr := nbio.bind(server, {nbio.IP4_Loopback, 0})
|
||||
ev(t, berr, nil)
|
||||
ep, eperr := nbio.bound_endpoint(server)
|
||||
ev(t, eperr, nil)
|
||||
|
||||
// Server.
|
||||
{
|
||||
nbio.poll_poly(server, .Receive, t, on_poll)
|
||||
|
||||
on_poll :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.poll.result, nbio.Poll_Result.Ready)
|
||||
}
|
||||
|
||||
buf: [128]byte
|
||||
nbio.recv_poly(server, {buf[:]}, t, on_recv)
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.recv.err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Client.
|
||||
{
|
||||
sock, cerr := nbio.create_udp_socket(.IP4)
|
||||
ev(t, cerr, nil)
|
||||
|
||||
// Make sure the server would block.
|
||||
nbio.timeout_poly3(time.Millisecond*10, t, sock, ep.port, on_timeout)
|
||||
|
||||
on_timeout :: proc(op: ^nbio.Operation, t: ^testing.T, sock: nbio.UDP_Socket, port: int) {
|
||||
nbio.send_poly(sock, {transmute([]byte)string("Hiya")}, t, on_send, {nbio.IP4_Loopback, port})
|
||||
}
|
||||
|
||||
on_send :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.send.err, nil)
|
||||
ev(t, op.send.sent, 4)
|
||||
|
||||
// Do another send after a bit, some backends don't trigger both ops when one was enough to
|
||||
// use up the socket.
|
||||
nbio.timeout_poly3(time.Millisecond*10, t, op.send.socket.(nbio.UDP_Socket), op.send.endpoint.port, on_timeout2)
|
||||
}
|
||||
|
||||
on_timeout2 :: proc(op: ^nbio.Operation, t: ^testing.T, sock: nbio.UDP_Socket, port: int) {
|
||||
nbio.send_poly(sock, {transmute([]byte)string("Hiya")}, t, on_send2, {nbio.IP4_Loopback, port})
|
||||
}
|
||||
|
||||
on_send2 :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.send.err, nil)
|
||||
ev(t, op.send.sent, 4)
|
||||
|
||||
nbio.close(op.send.socket.(nbio.UDP_Socket))
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
timeout :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
start := time.now()
|
||||
|
||||
nbio.timeout_poly2(time.Millisecond*20, t, start, on_timeout)
|
||||
|
||||
on_timeout :: proc(op: ^nbio.Operation, t: ^testing.T, start: time.Time) {
|
||||
since := time.since(start)
|
||||
log.infof("timeout ran after: %v", since)
|
||||
testing.expect(t, since >= time.Millisecond*19) // A ms grace, for some reason it is sometimes ran after 19.8ms.
|
||||
if since < 20 {
|
||||
log.warnf("timeout ran after: %v", since)
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
wake_up :: proc(t: ^testing.T) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
if event_loop_guard(t) {
|
||||
for _ in 0..<2 {
|
||||
sock, _ := open_next_available_local_port(t)
|
||||
|
||||
// Add an accept, with nobody dialling this should block the event loop forever.
|
||||
accept := nbio.accept(sock, proc(op: ^nbio.Operation) {
|
||||
log.error("shouldn't be called")
|
||||
})
|
||||
|
||||
// Make sure the accept is in progress.
|
||||
ev(t, nbio.tick(timeout=0), nil)
|
||||
|
||||
hit: bool
|
||||
thr := thread.create_and_start_with_poly_data2(nbio.current_thread_event_loop(), &hit, proc(l: ^nbio.Event_Loop, hit: ^bool) {
|
||||
hit^ = true
|
||||
nbio.wake_up(l)
|
||||
}, context)
|
||||
defer thread.destroy(thr)
|
||||
|
||||
// Should block forever until the thread calling wake_up will make it return.
|
||||
ev(t, nbio.tick(), nil)
|
||||
e(t, hit)
|
||||
|
||||
nbio.remove(accept)
|
||||
nbio.close(sock)
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
ev(t, nbio.tick(timeout=0), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
400
tests/core/nbio/net.odin
Normal file
400
tests/core/nbio/net.odin
Normal file
@@ -0,0 +1,400 @@
|
||||
package tests_nbio
|
||||
|
||||
import "core:mem"
|
||||
import "core:nbio"
|
||||
import "core:net"
|
||||
import "core:testing"
|
||||
import "core:time"
|
||||
import "core:log"
|
||||
|
||||
open_next_available_local_port :: proc(t: ^testing.T, addr: net.Address = net.IP4_Loopback, loc := #caller_location) -> (sock: net.TCP_Socket, ep: net.Endpoint) {
|
||||
err: net.Network_Error
|
||||
sock, err = nbio.listen_tcp({addr, 0})
|
||||
if err != nil {
|
||||
log.errorf("listen_tcp: %v", err, location=loc)
|
||||
return
|
||||
}
|
||||
|
||||
ep, err = net.bound_endpoint(sock)
|
||||
if err != nil {
|
||||
log.errorf("bound_endpoint: %v", err, location=loc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(test)
|
||||
client_and_server_send_recv :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
server, ep := open_next_available_local_port(t)
|
||||
|
||||
CONTENT :: [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
||||
|
||||
State :: struct {
|
||||
server: net.TCP_Socket,
|
||||
server_client: net.TCP_Socket,
|
||||
client: net.TCP_Socket,
|
||||
recv_buf: [20]byte,
|
||||
send_buf: [20]byte,
|
||||
}
|
||||
|
||||
state := State{
|
||||
server = server,
|
||||
send_buf = CONTENT,
|
||||
}
|
||||
|
||||
close_ok :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.close.err, nil)
|
||||
}
|
||||
|
||||
// Server
|
||||
{
|
||||
nbio.accept_poly2(server, t, &state, on_accept)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
|
||||
ev(t, op.accept.err, nil)
|
||||
|
||||
state.server_client = op.accept.client
|
||||
|
||||
log.debugf("accepted connection from: %v", op.accept.client_endpoint)
|
||||
|
||||
nbio.recv_poly2(state.server_client, {state.recv_buf[:]}, t, state, on_recv)
|
||||
}
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
|
||||
ev(t, op.recv.err, nil)
|
||||
ev(t, op.recv.received, 20)
|
||||
ev(t, state.recv_buf, CONTENT)
|
||||
|
||||
nbio.close_poly(state.server_client, t, close_ok)
|
||||
nbio.close_poly(state.server, t, close_ok)
|
||||
}
|
||||
|
||||
ev(t, nbio.tick(0), nil)
|
||||
}
|
||||
|
||||
// Client
|
||||
{
|
||||
nbio.dial_poly2(ep, t, &state, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
|
||||
ev(t, op.dial.err, nil)
|
||||
|
||||
state.client = op.dial.socket
|
||||
|
||||
nbio.send_poly2(state.client, {state.send_buf[:]}, t, state, on_send)
|
||||
}
|
||||
|
||||
on_send :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) {
|
||||
ev(t, op.send.err, nil)
|
||||
ev(t, op.send.sent, 20)
|
||||
|
||||
nbio.close_poly(state.client, t, close_ok)
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
close_and_remove_accept :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
server, _ := open_next_available_local_port(t)
|
||||
|
||||
accept := nbio.accept_poly(server, t, proc(_: ^nbio.Operation, t: ^testing.T) {
|
||||
testing.fail_now(t)
|
||||
})
|
||||
|
||||
ev(t, nbio.tick(0), nil)
|
||||
|
||||
nbio.close_poly(server, t, proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.close.err, nil)
|
||||
})
|
||||
|
||||
nbio.remove(accept)
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that when a client calls `close` on it's socket, `recv` returns with `0, nil` (connection closed).
|
||||
@(test)
|
||||
close_errors_recv :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
server, ep := open_next_available_local_port(t)
|
||||
|
||||
// Server
|
||||
{
|
||||
nbio.accept_poly(server, t, on_accept)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.accept.err, nil)
|
||||
|
||||
bytes := make([]byte, 128, context.temp_allocator)
|
||||
nbio.recv_poly(op.accept.client, {bytes}, t, on_recv)
|
||||
}
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.recv.received, 0)
|
||||
ev(t, op.recv.err, nil)
|
||||
}
|
||||
|
||||
ev(t, nbio.tick(0), nil)
|
||||
}
|
||||
|
||||
// Client
|
||||
{
|
||||
nbio.dial_poly(ep, t, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.dial.err, nil)
|
||||
nbio.close_poly(op.dial.socket, t, on_close)
|
||||
}
|
||||
|
||||
on_close :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.close.err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
ipv6 :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
server, ep := open_next_available_local_port(t, net.IP6_Loopback)
|
||||
|
||||
nbio.accept_poly(server, t, on_accept)
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.accept.err, nil)
|
||||
addr, is_ipv6 := op.accept.client_endpoint.address.(net.IP6_Address)
|
||||
e(t, is_ipv6)
|
||||
ev(t, addr, net.IP6_Loopback)
|
||||
e(t, op.accept.client_endpoint.port != 0)
|
||||
nbio.close(op.accept.client)
|
||||
nbio.close(op.accept.socket)
|
||||
}
|
||||
|
||||
nbio.dial_poly(ep, t, on_dial)
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.dial.err, nil)
|
||||
nbio.close(op.dial.socket)
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
accept_timeout :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
sock, _ := open_next_available_local_port(t)
|
||||
|
||||
hit: bool
|
||||
nbio.accept_poly2(sock, t, &hit, on_accept, timeout=time.Millisecond)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, hit: ^bool) {
|
||||
hit^ = true
|
||||
ev(t, op.accept.err, net.Accept_Error.Timeout)
|
||||
nbio.close(op.accept.socket)
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
|
||||
e(t, hit)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
poll_timeout :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
sock, err := nbio.create_udp_socket(.IP4)
|
||||
ev(t, err, nil)
|
||||
berr := nbio.bind(sock, {nbio.IP4_Loopback, 0})
|
||||
ev(t, berr, nil)
|
||||
|
||||
nbio.poll_poly(sock, .Receive, t, on_poll, time.Millisecond)
|
||||
on_poll :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.poll.result, nbio.Poll_Result.Timeout)
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This test walks through the scenario where a user wants to `poll` in order to check if some other package (in this case `core:net`),
|
||||
would be able to do an operation without blocking.
|
||||
|
||||
It also tests whether a poll can be issues when it is already in a ready state.
|
||||
And it tests big send/recv buffers being handled properly.
|
||||
*/
|
||||
@(test)
|
||||
poll :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
// testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
can_recv: bool
|
||||
|
||||
sock, ep := open_next_available_local_port(t)
|
||||
|
||||
// Server
|
||||
{
|
||||
nbio.accept_poly2(sock, t, &can_recv, on_accept)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) {
|
||||
ev(t, op.accept.err, nil)
|
||||
|
||||
check_recv :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool, client: net.TCP_Socket) {
|
||||
// Not ready to unblock the client yet, requeue for after 10ms.
|
||||
if !can_recv^ {
|
||||
nbio.timeout_poly3(time.Millisecond * 10, t, can_recv, client, check_recv)
|
||||
return
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
|
||||
// Connection was closed by client, close server.
|
||||
if op.type == .Recv && op.recv.received == 0 && op.recv.err == nil {
|
||||
nbio.close(client)
|
||||
return
|
||||
}
|
||||
|
||||
if op.type == .Recv {
|
||||
log.debugf("received %M this time", op.recv.received)
|
||||
}
|
||||
|
||||
// Receive some data to unblock the client, which should complete the poll it does, allowing it to send data again.
|
||||
buf, mem_err := make([]byte, mem.Gigabyte, context.temp_allocator)
|
||||
ev(t, mem_err, nil)
|
||||
nbio.recv_poly3(client, {buf}, t, can_recv, client, check_recv)
|
||||
}
|
||||
nbio.timeout_poly3(time.Millisecond * 10, t, can_recv, op.accept.client, check_recv)
|
||||
}
|
||||
|
||||
ev(t, nbio.tick(0), nil)
|
||||
}
|
||||
|
||||
// Client
|
||||
{
|
||||
nbio.dial_poly2(ep, t, &can_recv, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) {
|
||||
ev(t, op.dial.err, nil)
|
||||
|
||||
// Do a poll even though we know it's ready, so we can test that all implementations can handle that.
|
||||
nbio.poll_poly2(op.dial.socket, .Send, t, can_recv, on_poll1)
|
||||
}
|
||||
|
||||
on_poll1 :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) {
|
||||
ev(t, op.poll.result, nil)
|
||||
|
||||
// Send 4 GB of data, which in my experience causes a Would_Block error because we filled up the internal buffer.
|
||||
buf, mem_err := make([]byte, mem.Gigabyte*4, context.temp_allocator)
|
||||
ev(t, mem_err, nil)
|
||||
|
||||
// Use `core:net` as example external code that doesn't care about the event loop.
|
||||
net.set_blocking(op.poll.socket, false)
|
||||
n, send_err := net.send(op.poll.socket, buf)
|
||||
ev(t, send_err, net.TCP_Send_Error.Would_Block)
|
||||
|
||||
log.debugf("blocking after %M", n)
|
||||
|
||||
// Tell the server it can start issueing recv calls, so it unblocks us.
|
||||
can_recv^ = true
|
||||
|
||||
// Now poll again, when the server reads enough data it should complete, telling us we can send without blocking again.
|
||||
nbio.poll_poly(op.poll.socket, .Send, t, on_poll2)
|
||||
}
|
||||
|
||||
on_poll2 :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.poll.result, nil)
|
||||
|
||||
buf: [128]byte
|
||||
bytes_written, send_err := net.send(op.poll.socket, buf[:])
|
||||
ev(t, bytes_written, 128)
|
||||
ev(t, send_err, nil)
|
||||
|
||||
nbio.close(op.poll.socket.(net.TCP_Socket))
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
nbio.close(sock)
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
sendfile :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
CONTENT :: #load(#file)
|
||||
|
||||
sock, ep := open_next_available_local_port(t)
|
||||
|
||||
// Server
|
||||
{
|
||||
nbio.accept_poly(sock, t, on_accept)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.accept.err, nil)
|
||||
e(t, op.accept.client != 0)
|
||||
|
||||
log.debugf("connection from: %v", op.accept.client_endpoint)
|
||||
nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open)
|
||||
}
|
||||
|
||||
on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
|
||||
ev(t, op.open.err, nil)
|
||||
|
||||
nbio.sendfile_poly2(client, op.open.handle, t, server, on_sendfile)
|
||||
}
|
||||
|
||||
on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) {
|
||||
ev(t, op.sendfile.err, nil)
|
||||
ev(t, op.sendfile.sent, len(CONTENT))
|
||||
|
||||
nbio.close(op.sendfile.file)
|
||||
nbio.close(op.sendfile.socket)
|
||||
nbio.close(server)
|
||||
}
|
||||
}
|
||||
|
||||
// Client
|
||||
{
|
||||
nbio.dial_poly(ep, t, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.dial.err, nil)
|
||||
|
||||
buf := make([]byte, len(CONTENT), context.temp_allocator)
|
||||
nbio.recv_poly(op.dial.socket, {buf}, t, on_recv, all=true)
|
||||
}
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.recv.err, nil)
|
||||
ev(t, op.recv.received, len(CONTENT))
|
||||
ev(t, string(op.recv.bufs[0]), string(CONTENT))
|
||||
|
||||
nbio.close(op.recv.socket.(net.TCP_Socket))
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
247
tests/core/nbio/remove.odin
Normal file
247
tests/core/nbio/remove.odin
Normal file
@@ -0,0 +1,247 @@
|
||||
package tests_nbio
|
||||
|
||||
import "core:nbio"
|
||||
import "core:net"
|
||||
import "core:testing"
|
||||
import "core:time"
|
||||
import "core:log"
|
||||
|
||||
// Removals are pretty complex.
|
||||
|
||||
@(test)
|
||||
immediate_remove_of_sendfile :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
sock, ep := open_next_available_local_port(t)
|
||||
|
||||
// Server
|
||||
{
|
||||
nbio.accept_poly(sock, t, on_accept)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.accept.err, nil)
|
||||
e(t, op.accept.client != 0)
|
||||
|
||||
log.debugf("connection from: %v", op.accept.client_endpoint)
|
||||
nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open)
|
||||
}
|
||||
|
||||
on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
|
||||
ev(t, op.open.err, nil)
|
||||
e(t, op.open.handle != 0)
|
||||
|
||||
sendfile_op := nbio.sendfile_poly2(client, op.open.handle, t, server, on_sendfile)
|
||||
|
||||
// oh no changed my mind.
|
||||
nbio.remove(sendfile_op)
|
||||
|
||||
nbio.close(op.open.handle)
|
||||
nbio.close(client)
|
||||
nbio.close(server)
|
||||
}
|
||||
|
||||
on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) {
|
||||
log.error("on_sendfile shouldn't be called")
|
||||
}
|
||||
}
|
||||
|
||||
// Client
|
||||
{
|
||||
nbio.dial_poly(ep, t, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.dial.err, nil)
|
||||
|
||||
buf := make([]byte, 128, context.temp_allocator)
|
||||
nbio.recv_poly(op.dial.socket, {buf}, t, on_recv)
|
||||
}
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.recv.err, nil)
|
||||
|
||||
nbio.close(op.recv.socket.(net.TCP_Socket))
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
immediate_remove_of_sendfile_without_stat :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
sock, ep := open_next_available_local_port(t)
|
||||
|
||||
// Server
|
||||
{
|
||||
nbio.accept_poly(sock, t, on_accept)
|
||||
|
||||
on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.accept.err, nil)
|
||||
e(t, op.accept.client != 0)
|
||||
|
||||
log.debugf("connection from: %v", op.accept.client_endpoint)
|
||||
nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open)
|
||||
}
|
||||
|
||||
on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
|
||||
ev(t, op.open.err, nil)
|
||||
e(t, op.open.handle != 0)
|
||||
|
||||
nbio.stat_poly3(op.open.handle, t, server, client, on_stat)
|
||||
}
|
||||
|
||||
on_stat :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) {
|
||||
ev(t, op.stat.err, nil)
|
||||
|
||||
sendfile_op := nbio.sendfile_poly2(client, op.stat.handle, t, server, on_sendfile, nbytes=int(op.stat.size))
|
||||
|
||||
// oh no changed my mind.
|
||||
nbio.remove(sendfile_op)
|
||||
|
||||
nbio.timeout_poly3(time.Millisecond * 10, op.stat.handle, client, server, proc(op: ^nbio.Operation, p1: nbio.Handle, p2, p3: net.TCP_Socket){
|
||||
nbio.close(p1)
|
||||
nbio.close(p2)
|
||||
nbio.close(p3)
|
||||
})
|
||||
}
|
||||
|
||||
on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) {
|
||||
log.error("on_sendfile shouldn't be called")
|
||||
}
|
||||
}
|
||||
|
||||
// Client
|
||||
{
|
||||
nbio.dial_poly(ep, t, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.dial.err, nil)
|
||||
|
||||
buf := make([]byte, 128, context.temp_allocator)
|
||||
nbio.recv_poly(op.dial.socket, {buf}, t, on_recv)
|
||||
}
|
||||
|
||||
on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.recv.err, nil)
|
||||
|
||||
nbio.close(op.recv.socket.(net.TCP_Socket))
|
||||
}
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Open should free the temporary memory allocated for the path when removed.
|
||||
// Can't really test that though, so should be checked manually that the internal callback is called but not the external.
|
||||
@(test)
|
||||
remove_open :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
open := nbio.open(#file, on_open)
|
||||
nbio.remove(open)
|
||||
|
||||
on_open :: proc(op: ^nbio.Operation) {
|
||||
log.error("on_open shouldn't be called")
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Dial should close the socket when removed.
|
||||
// Can't really test that though, so should be checked manually that the internal callback is called but not the external.
|
||||
@(test)
|
||||
remove_dial :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
sock, ep := open_next_available_local_port(t)
|
||||
defer nbio.close(sock)
|
||||
|
||||
dial := nbio.dial(ep, on_dial)
|
||||
nbio.remove(dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation) {
|
||||
log.error("on_dial shouldn't be called")
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
remove_next_tick :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
nt := nbio.next_tick_poly(t, proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
log.error("shouldn't be called")
|
||||
})
|
||||
nbio.remove(nt)
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
remove_timeout :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
hit: bool
|
||||
timeout := nbio.timeout_poly(time.Second, &hit, proc(_: ^nbio.Operation, hit: ^bool) {
|
||||
hit^ = true
|
||||
})
|
||||
|
||||
nbio.remove(timeout)
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
|
||||
e(t, !hit)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
remove_multiple_poll :: proc(t: ^testing.T) {
|
||||
if event_loop_guard(t) {
|
||||
testing.set_fail_timeout(t, time.Minute)
|
||||
|
||||
sock, ep := open_next_available_local_port(t)
|
||||
defer nbio.close(sock)
|
||||
|
||||
hit: bool
|
||||
|
||||
first := nbio.poll(sock, .Receive, on_poll)
|
||||
nbio.poll_poly2(sock, .Receive, t, &hit, on_poll2)
|
||||
|
||||
on_poll :: proc(op: ^nbio.Operation) {
|
||||
log.error("shouldn't be called")
|
||||
}
|
||||
|
||||
on_poll2 :: proc(op: ^nbio.Operation, t: ^testing.T, hit: ^bool) {
|
||||
ev(t, op.poll.result, nbio.Poll_Result.Ready)
|
||||
hit^ = true
|
||||
}
|
||||
|
||||
ev(t, nbio.tick(0), nil)
|
||||
|
||||
nbio.remove(first)
|
||||
|
||||
ev(t, nbio.tick(0), nil)
|
||||
|
||||
nbio.dial_poly(ep, t, on_dial)
|
||||
|
||||
on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) {
|
||||
ev(t, op.dial.err, nil)
|
||||
}
|
||||
|
||||
ev(t, nbio.run(), nil)
|
||||
e(t, hit)
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
A test suite for `core:net`
|
||||
*/
|
||||
#+build !netbsd
|
||||
#+build !openbsd
|
||||
#+feature dynamic-literals
|
||||
package test_core_net
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ download_assets :: proc "contextless" () {
|
||||
@(require) import "math/noise"
|
||||
@(require) import "math/rand"
|
||||
@(require) import "mem"
|
||||
@(require) import "nbio"
|
||||
@(require) import "net"
|
||||
@(require) import "odin"
|
||||
@(require) import "os"
|
||||
@@ -45,6 +46,7 @@ download_assets :: proc "contextless" () {
|
||||
@(require) import "sync"
|
||||
@(require) import "sync/chan"
|
||||
@(require) import "sys/posix"
|
||||
@(require) import "sys/kqueue"
|
||||
@(require) import "sys/windows"
|
||||
@(require) import "text/i18n"
|
||||
@(require) import "text/match"
|
||||
|
||||
56
tests/core/sys/kqueue/structs.odin
Normal file
56
tests/core/sys/kqueue/structs.odin
Normal file
@@ -0,0 +1,56 @@
|
||||
#+build darwin, freebsd, openbsd, netbsd
|
||||
package tests_core_sys_kqueue
|
||||
|
||||
import "core:strings"
|
||||
import "core:testing"
|
||||
import os "core:os/os2"
|
||||
|
||||
@(test)
|
||||
structs :: proc(t: ^testing.T) {
|
||||
{
|
||||
c_compiler := os.get_env("CC", context.temp_allocator)
|
||||
if c_compiler == "" {
|
||||
c_compiler = "clang"
|
||||
}
|
||||
|
||||
c_compilation, c_start_err := os.process_start({
|
||||
command = {c_compiler, #directory + "/structs/structs.c", "-o", #directory + "/structs/c_structs"},
|
||||
stdout = os.stdout,
|
||||
stderr = os.stderr,
|
||||
})
|
||||
testing.expect_value(t, c_start_err, nil)
|
||||
|
||||
o_compilation, o_start_err := os.process_start({
|
||||
command = {ODIN_ROOT + "/odin", "build", #directory + "/structs", "-out:" + #directory + "/structs/odin_structs"},
|
||||
stdout = os.stdout,
|
||||
stderr = os.stderr,
|
||||
})
|
||||
testing.expect_value(t, o_start_err, nil)
|
||||
|
||||
c_status, c_err := os.process_wait(c_compilation)
|
||||
testing.expect_value(t, c_err, nil)
|
||||
testing.expect_value(t, c_status.exit_code, 0)
|
||||
|
||||
o_status, o_err := os.process_wait(o_compilation)
|
||||
testing.expect_value(t, o_err, nil)
|
||||
testing.expect_value(t, o_status.exit_code, 0)
|
||||
}
|
||||
|
||||
c_status, c_stdout, c_stderr, c_err := os.process_exec({command={#directory + "/structs/c_structs"}}, context.temp_allocator)
|
||||
testing.expect_value(t, c_err, nil)
|
||||
testing.expect_value(t, c_status.exit_code, 0)
|
||||
testing.expect_value(t, string(c_stderr), "")
|
||||
|
||||
o_status, o_stdout, o_stderr, o_err := os.process_exec({command={#directory + "/structs/odin_structs"}}, context.temp_allocator)
|
||||
testing.expect_value(t, o_err, nil)
|
||||
testing.expect_value(t, o_status.exit_code, 0)
|
||||
testing.expect_value(t, string(o_stderr), "")
|
||||
|
||||
testing.expect(t, strings.trim_space(string(c_stdout)) != "")
|
||||
|
||||
testing.expect_value(
|
||||
t,
|
||||
strings.trim_space(string(o_stdout)),
|
||||
strings.trim_space(string(c_stdout)),
|
||||
)
|
||||
}
|
||||
63
tests/core/sys/kqueue/structs/structs.c
Normal file
63
tests/core/sys/kqueue/structs/structs.c
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/event.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
printf("kevent %zu %zu\n", sizeof(struct kevent), _Alignof(struct kevent));
|
||||
printf("kevent.ident %zu\n", offsetof(struct kevent, ident));
|
||||
printf("kevent.filter %zu\n", offsetof(struct kevent, filter));
|
||||
printf("kevent.flags %zu\n", offsetof(struct kevent, flags));
|
||||
printf("kevent.fflags %zu\n", offsetof(struct kevent, fflags));
|
||||
printf("kevent.data %zu\n", offsetof(struct kevent, data));
|
||||
printf("kevent.udata %zu\n", offsetof(struct kevent, udata));
|
||||
|
||||
printf("EV_ADD %d\n", EV_ADD);
|
||||
printf("EV_DELETE %d\n", EV_DELETE);
|
||||
printf("EV_ENABLE %d\n", EV_ENABLE);
|
||||
printf("EV_DISABLE %d\n", EV_DISABLE);
|
||||
printf("EV_ONESHOT %d\n", EV_ONESHOT);
|
||||
printf("EV_CLEAR %d\n", EV_CLEAR);
|
||||
printf("EV_RECEIPT %d\n", EV_RECEIPT);
|
||||
printf("EV_DISPATCH %d\n", EV_DISPATCH);
|
||||
printf("EV_ERROR %d\n", EV_ERROR);
|
||||
printf("EV_EOF %d\n", EV_EOF);
|
||||
|
||||
printf("EVFILT_READ %d\n", EVFILT_READ);
|
||||
printf("EVFILT_WRITE %d\n", EVFILT_WRITE);
|
||||
printf("EVFILT_AIO %d\n", EVFILT_AIO);
|
||||
printf("EVFILT_VNODE %d\n", EVFILT_VNODE);
|
||||
printf("EVFILT_PROC %d\n", EVFILT_PROC);
|
||||
printf("EVFILT_SIGNAL %d\n", EVFILT_SIGNAL);
|
||||
printf("EVFILT_TIMER %d\n", EVFILT_TIMER);
|
||||
printf("EVFILT_USER %d\n", EVFILT_USER);
|
||||
|
||||
printf("NOTE_SECONDS %u\n", NOTE_SECONDS);
|
||||
printf("NOTE_USECONDS %u\n", NOTE_USECONDS);
|
||||
printf("NOTE_NSECONDS %u\n", NOTE_NSECONDS);
|
||||
#if defined(NOTE_ABSOLUTE)
|
||||
printf("NOTE_ABSOLUTE %u\n", NOTE_ABSOLUTE);
|
||||
#else
|
||||
printf("NOTE_ABSOLUTE %u\n", NOTE_ABSTIME);
|
||||
#endif
|
||||
|
||||
printf("NOTE_LOWAT %u\n", NOTE_LOWAT);
|
||||
|
||||
printf("NOTE_DELETE %u\n", NOTE_DELETE);
|
||||
printf("NOTE_WRITE %u\n", NOTE_WRITE);
|
||||
printf("NOTE_EXTEND %u\n", NOTE_EXTEND);
|
||||
printf("NOTE_ATTRIB %u\n", NOTE_ATTRIB);
|
||||
printf("NOTE_LINK %u\n", NOTE_LINK);
|
||||
printf("NOTE_RENAME %u\n", NOTE_RENAME);
|
||||
printf("NOTE_REVOKE %u\n", NOTE_REVOKE);
|
||||
|
||||
printf("NOTE_EXIT %u\n", NOTE_EXIT);
|
||||
printf("NOTE_FORK %u\n", NOTE_FORK);
|
||||
printf("NOTE_EXEC %u\n", NOTE_EXEC);
|
||||
|
||||
printf("NOTE_TRIGGER %u\n", NOTE_TRIGGER);
|
||||
printf("NOTE_FFAND %u\n", NOTE_FFAND);
|
||||
printf("NOTE_FFOR %u\n", NOTE_FFOR);
|
||||
printf("NOTE_FFCOPY %u\n", NOTE_FFCOPY);
|
||||
return 0;
|
||||
}
|
||||
58
tests/core/sys/kqueue/structs/structs.odin
Normal file
58
tests/core/sys/kqueue/structs/structs.odin
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:sys/kqueue"
|
||||
|
||||
main :: proc() {
|
||||
fmt.println("kevent", size_of(kqueue.KEvent), align_of(kqueue.KEvent))
|
||||
fmt.println("kevent.ident", offset_of(kqueue.KEvent, ident))
|
||||
fmt.println("kevent.filter", offset_of(kqueue.KEvent, filter))
|
||||
fmt.println("kevent.flags", offset_of(kqueue.KEvent, flags))
|
||||
fmt.println("kevent.fflags", offset_of(kqueue.KEvent, fflags))
|
||||
fmt.println("kevent.data", offset_of(kqueue.KEvent, data))
|
||||
fmt.println("kevent.udata", offset_of(kqueue.KEvent, udata))
|
||||
|
||||
fmt.println("EV_ADD", transmute(kqueue._Flags_Backing)kqueue.Flags{.Add})
|
||||
fmt.println("EV_DELETE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Delete})
|
||||
fmt.println("EV_ENABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Enable})
|
||||
fmt.println("EV_DISABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Disable})
|
||||
fmt.println("EV_ONESHOT", transmute(kqueue._Flags_Backing)kqueue.Flags{.One_Shot})
|
||||
fmt.println("EV_CLEAR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Clear})
|
||||
fmt.println("EV_RECEIPT", transmute(kqueue._Flags_Backing)kqueue.Flags{.Receipt})
|
||||
fmt.println("EV_DISPATCH", transmute(kqueue._Flags_Backing)kqueue.Flags{.Dispatch})
|
||||
fmt.println("EV_ERROR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Error})
|
||||
fmt.println("EV_EOF", transmute(kqueue._Flags_Backing)kqueue.Flags{.EOF})
|
||||
|
||||
fmt.println("EVFILT_READ", int(kqueue.Filter.Read))
|
||||
fmt.println("EVFILT_WRITE", int(kqueue.Filter.Write))
|
||||
fmt.println("EVFILT_AIO", int(kqueue.Filter.AIO))
|
||||
fmt.println("EVFILT_VNODE", int(kqueue.Filter.VNode))
|
||||
fmt.println("EVFILT_PROC", int(kqueue.Filter.Proc))
|
||||
fmt.println("EVFILT_SIGNAL", int(kqueue.Filter.Signal))
|
||||
fmt.println("EVFILT_TIMER", int(kqueue.Filter.Timer))
|
||||
fmt.println("EVFILT_USER", int(kqueue.Filter.User))
|
||||
|
||||
fmt.println("NOTE_SECONDS", transmute(u32)kqueue.Timer_Flags{.Seconds})
|
||||
fmt.println("NOTE_USECONDS", transmute(u32)kqueue.Timer_Flags{.USeconds})
|
||||
fmt.println("NOTE_NSECONDS", transmute(u32)kqueue.TIMER_FLAGS_NSECONDS)
|
||||
fmt.println("NOTE_ABSOLUTE", transmute(u32)kqueue.Timer_Flags{.Absolute})
|
||||
|
||||
fmt.println("NOTE_LOWAT", transmute(u32)kqueue.RW_Flags{.Low_Water_Mark})
|
||||
|
||||
fmt.println("NOTE_DELETE", transmute(u32)kqueue.VNode_Flags{.Delete})
|
||||
fmt.println("NOTE_WRITE", transmute(u32)kqueue.VNode_Flags{.Write})
|
||||
fmt.println("NOTE_EXTEND", transmute(u32)kqueue.VNode_Flags{.Extend})
|
||||
fmt.println("NOTE_ATTRIB", transmute(u32)kqueue.VNode_Flags{.Attrib})
|
||||
fmt.println("NOTE_LINK", transmute(u32)kqueue.VNode_Flags{.Link})
|
||||
fmt.println("NOTE_RENAME", transmute(u32)kqueue.VNode_Flags{.Rename})
|
||||
fmt.println("NOTE_REVOKE", transmute(u32)kqueue.VNode_Flags{.Revoke})
|
||||
|
||||
fmt.println("NOTE_EXIT", transmute(u32)kqueue.Proc_Flags{.Exit})
|
||||
fmt.println("NOTE_FORK", transmute(u32)kqueue.Proc_Flags{.Fork})
|
||||
fmt.println("NOTE_EXEC", transmute(u32)kqueue.Proc_Flags{.Exec})
|
||||
|
||||
fmt.println("NOTE_TRIGGER", transmute(u32)kqueue.User_Flags{.Trigger})
|
||||
fmt.println("NOTE_FFAND", transmute(u32)kqueue.User_Flags{.FFAnd})
|
||||
fmt.println("NOTE_FFOR", transmute(u32)kqueue.User_Flags{.FFOr})
|
||||
fmt.println("NOTE_FFCOPY", transmute(u32)kqueue.USER_FLAGS_COPY)
|
||||
}
|
||||
Reference in New Issue
Block a user