Add work queue for requests

This commit is contained in:
Hugo Mårdbrink 2025-11-10 22:11:26 +01:00
parent 83b39b15f6
commit a52c0cc2fd
5 changed files with 193 additions and 74 deletions

View file

@ -1,5 +1,6 @@
package fjord
import "base:runtime"
import "core:bufio"
import "core:bytes"
import "core:io"
@ -9,6 +10,7 @@ import "core:path/slashpath"
import "core:strconv"
import "core:strings"
import "core:sync"
import "core:thread"
import "core:unicode"
import http "http"
@ -21,8 +23,11 @@ Server :: struct($Error_Type: typeid) {
http.Response,
Error_Type,
),
router: Router(Error_Type),
shutdown: bool,
router: Router(Error_Type),
work_queue: WorkQueue,
worker_count: int,
worker_threads: [dynamic]^thread.Thread,
}
@(private)
@ -61,6 +66,9 @@ server_init :: proc(
endpoint: net.Endpoint,
allocator := context.allocator,
) {
WORKER_COUNT :: 8
QUEUE_CAPACITY :: 252
server^ = Server(Error_Type) {
endpoint = endpoint,
not_found_handler = proc(
@ -78,11 +86,20 @@ server_init :: proc(
return http_response, nil
},
shutdown = false,
worker_count = WORKER_COUNT,
worker_threads = make([dynamic]^thread.Thread, 0, WORKER_COUNT, allocator),
}
work_queue_init(&server.work_queue, QUEUE_CAPACITY, allocator)
router_init(&server.router, allocator)
}
server_destroy :: proc(server: ^Server($Error_Type)) {
for t in server.worker_threads {
thread.destroy(t)
}
delete(server.worker_threads)
work_queue_destroy(&server.work_queue)
router_destroy(&server.router)
}
@ -122,76 +139,97 @@ server_shutdown :: proc(server: ^Server($Error_Type)) {
sync.atomic_store(&server.shutdown, true)
}
listen_and_serve :: proc(server: ^Server($Error_Type)) {
@(private)
serve :: proc(server: ^Server($Error_Type), client_socket: net.TCP_Socket) {
data, read_err := read_connection(client_socket, context.temp_allocator)
if read_err != nil {
log.warnf("Failed to read request, reason: %s", read_err)
return
}
http_request, unmarshall_err := http.unmarshall_request(
data,
context.temp_allocator,
)
if unmarshall_err != nil {
log.warnf("Failed to unmarshall request, reason: %s", unmarshall_err)
return
}
handler: #type proc(request: ^http.Request) -> (http.Response, Error_Type)
ok: bool
// todo: should sanitize better
path := strings.split(http_request.path, "/", context.temp_allocator)[1:]
handler, http_request.path_variables, ok = router_lookup(
&server.router,
http_request.method,
path,
context.temp_allocator,
)
if !ok do handler = server.not_found_handler
http_response, handler_err := handler(&http_request)
if handler_err != nil {
log.warnf("Handler failed with error: %s", handler_err)
return
}
marshalled_response := http.marshall_response(
&http_response,
context.temp_allocator,
)
net.send_tcp(client_socket, marshalled_response)
}
@(private)
worker_thread :: proc(server: ^Server($Error_Type)) {
for {
socket, ok := work_queue_dequeue(&server.work_queue)
if !ok do break
serve(server, socket)
net.close(socket)
free_all(context.temp_allocator)
}
}
server_listen_and_serve :: proc(server: ^Server($Error_Type)) {
server_socket, net_err := net.listen_tcp(server.endpoint)
log.assert(net_err == nil, "Couldn't create TCP socket")
defer net.close(server_socket)
for i in 0 ..< server.worker_count {
t := thread.create(proc(t: ^thread.Thread) {
server := cast(^Server(Error_Type))t.data
worker_thread(server)
})
log.assertf(t != nil, "Thread creation failed")
t.data = rawptr(server)
thread.start(t)
append(&server.worker_threads, t)
}
for !sync.atomic_load(&server.shutdown) {
client_socket, source, net_err := net.accept_tcp(server_socket)
client_socket, _, net_err := net.accept_tcp(server_socket)
if net_err != nil {
log.warnf("Failed to accept TCP connection, reason: %s", net_err)
continue
}
defer net.close(client_socket)
data, read_err := read_connection(
client_socket,
context.temp_allocator,
)
if read_err != nil {
log.warnf("Failed to read request, reason: %s", read_err)
continue
ok := work_queue_enqueue(&server.work_queue, client_socket)
if !ok {
log.warn("Work queue full, rejecting connection")
net.close(client_socket)
}
}
http_request, unmarshall_err := http.unmarshall_request(
data,
context.temp_allocator,
)
if unmarshall_err != nil {
log.warnf(
"Failed to unmarshall request, reason: %s",
unmarshall_err,
)
continue
}
work_queue_shutdown(&server.work_queue)
handler: #type proc(
request: ^http.Request,
) -> (
http.Response,
Error_Type,
)
ok: bool
// todo: should sanitize better
path := strings.split(
http_request.path,
"/",
context.temp_allocator,
)[1:]
handler, http_request.path_variables, ok = router_lookup(
&server.router,
http_request.method,
path,
context.temp_allocator,
)
if !ok do handler = server.not_found_handler
http_response, handler_err := handler(&http_request)
if handler_err != nil {
log.warnf("Handler failed with error: %s", handler_err)
continue
}
marshalled_response := http.marshall_response(
&http_response,
context.temp_allocator,
)
net.send_tcp(client_socket, marshalled_response)
free_all(context.temp_allocator)
for t in server.worker_threads {
thread.join(t)
}
}