const std = @import("std");
fn handle(res: *std.http.Server.Response, aa: std.heap.ArenaAllocator) void {
defer aa.deinit();
defer res.deinit();
if (res.wait()) {
res.status = .ok;
const content = "<html><body>message from Server !</body></html>";
res.transfer_encoding = .{ .content_length = content.len };
if (res.headers.append("content-type", "text/html")) {} else |_| {
std.log.err("res.headers.append error!", .{});
}
if (res.send()) {} else |_| {
std.log.err("res.send error!", .{});
}
if (res.writer().writeAll(content)) {} else |_| {
std.log.err("res.finish error!", .{});
}
if (res.finish()) {} else |_| {
std.log.err("res.finish error!", .{});
}
} else |_| {
// Do nothing
}
if (res.state != .finished) {
std.log.err("request wasn't finished!", .{});
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.safety = true,
.verbose_log = false,
.thread_safe = true,
}){};
const allocator = gpa.allocator();
var tp = std.Thread.Pool{ .threads = &[_]std.Thread{}, .allocator = allocator };
try tp.init(.{ .allocator = allocator, .n_jobs = 15 });
defer tp.deinit();
var svr_internal = std.http.Server.init(allocator, .{ .reuse_address = true });
defer svr_internal.deinit();
const address = std.net.Address{ .in = std.net.Ip4Address.init(.{ 127, 0, 0, 1 }, 8080) };
try svr_internal.listen(address);
var index: u32 = 0;
while (true) {
index += 1;
var aa = std.heap.ArenaAllocator.init(allocator); // will be freed by the spawned thread.
var conn = try svr_internal.accept(.{ .allocator = aa.allocator(), .header_strategy = .{ .dynamic = 8192 } });
std.debug.print("server thread {*} listening on {}\n", .{ &conn, address });
try tp.spawn(handle, .{ &conn, aa });
}
}
The code above return the same object at address http.Server.Response@ed6a3f5f70.
Please help, Thanks in advance.
server thread http.Server.Response@ed6a3f5f70 listening on 127.0.0.1:8080
server thread http.Server.Response@ed6a3f5f70 listening on 127.0.0.1:8080
server thread http.Server.Response@ed6a3f5f70 listening on 127.0.0.1:8080
You are taking the address of var conn = ...
which is the location on the stack where you are storing the return value of accept
.
Thanks for reply. Does this means the first returned conn is always overwrited by second. how to avoid this. how can I have different conn kept, create a new function? Or Is there anyway move conn to heap ,so it will not overwrited.
const std = @import("std");
fn handle(res: *std.http.Server.Response, aa: std.heap.ArenaAllocator) void {
defer aa.deinit();
defer res.deinit();
if (res.wait()) {
res.status = .ok;
const content = "<html><body>message from Server !</body></html>";
res.transfer_encoding = .{ .content_length = content.len };
if (res.headers.append("content-type", "text/html")) {} else |_| {
std.log.err("res.headers.append error!", .{});
}
if (res.send()) {} else |_| {
std.log.err("res.send error!", .{});
}
if (res.writer().writeAll(content)) {} else |_| {
std.log.err("res.finish error!", .{});
}
if (res.finish()) {} else |_| {
std.log.err("res.finish error!", .{});
}
} else |_| {
// Do nothing
}
if (res.state != .finished) {
std.log.err("request wasn't finished!", .{});
}
}
fn workers(svr_internal: *std.http.Server, tp: *std.Thread.Pool, allocator: std.mem.Allocator) !void {
var aa = std.heap.ArenaAllocator.init(allocator); // will be freed by the spawned thread.
var conn = try svr_internal.accept(.{ .allocator = aa.allocator(), .header_strategy = .{ .dynamic = 8192 } });
std.debug.print("server thread {*} listening on {}\n", .{ &conn, 8080 });
try tp.spawn(handle, .{ &conn, aa });
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.safety = true,
.verbose_log = false,
.thread_safe = true,
}){};
const allocator = gpa.allocator();
var tp = std.Thread.Pool{ .threads = &[_]std.Thread{}, .allocator = allocator };
try tp.init(.{ .allocator = allocator, .n_jobs = 15 });
defer tp.deinit();
var svr_internal = std.http.Server.init(allocator, .{ .reuse_address = true });
defer svr_internal.deinit();
const address = std.net.Address{ .in = std.net.Ip4Address.init(.{ 127, 0, 0, 1 }, 8080) };
try svr_internal.listen(address);
var index: u32 = 0;
while (true) {
index += 1;
try workers(&svr_internal, &tp, allocator);
}
}
I changed the code, the conn is in different function now. But the addresses of conn is always same.
server thread http.Server.Response@96291f5df0 listening on 8080
server thread http.Server.Response@96291f5df0 listening on 8080
server thread http.Server.Response@96291f5df0 listening on 8080
The problem is you have only created one location to store the Response struct - on the stack in the main thread. The address is always the same because you are always returning the same address. To solve this, you’ll need to create a Response for each thread. I recommend using a MemoryPool
:
// outside the loop
var pool = std.heap.MemoryPool(std.http.Server.Response).init(allocator);
while (true) {
// inside the loop
// get a pointer to a new `Response` struct
var conn = try pool.create();
// assign the return value of accept to the memory location pointed to by conn
conn.* = try svr_internal.accept(.{ .allocator = aa.allocator(), .header_strategy = .{ .dynamic = 8192 } });
try tp.spawn(handle, .{ conn, aa, pool });
}
Now inside of handle you will need to destroy
the Response object:
fn handle(res: *std.http.Server.Response, aa: std.heap.ArenaAllocator, pool: *std.heap.MemoryPool(std.http.Server.Response) void {
defer pool.destroy(res);
// rest of your code ...
}
I have not tested this code but I believe it should work.
Thanks for reply.
const std = @import("std");
fn handle(
res: *std.http.Server.Response,
aa: std.heap.ArenaAllocator,
pool: *std.heap.MemoryPool(std.http.Server.Response),
) void {
defer pool.destroy(res);
defer aa.deinit();
defer res.deinit();
if (res.wait()) {
res.status = .ok;
const content = "<html><body>message from Server !</body></html>";
res.transfer_encoding = .{ .content_length = content.len };
if (res.headers.append("content-type", "text/html")) {} else |_| {
std.log.err("res.headers.append error!", .{});
}
if (res.send()) {} else |_| {
std.log.err("res.send error!", .{});
}
if (res.writer().writeAll(content)) {} else |_| {
std.log.err("res.writer error!", .{});
}
if (res.finish()) {} else |_| {
std.log.err("res.finish error!", .{});
}
} else |_| {
// Do nothing
}
if (res.state != .finished) {
std.log.err("request wasn't finished!", .{});
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.safety = true,
.verbose_log = false,
.thread_safe = true,
}){};
const allocator = gpa.allocator();
var tp = std.Thread.Pool{ .threads = &[_]std.Thread{}, .allocator = allocator };
try tp.init(.{ .allocator = allocator, .n_jobs = 15 });
defer tp.deinit();
var svr_internal = std.http.Server.init(allocator, .{ .reuse_address = true });
defer svr_internal.deinit();
const address = std.net.Address{ .in = std.net.Ip4Address.init(.{ 127, 0, 0, 1 }, 8080) };
try svr_internal.listen(address);
// outside the loop
var pool = std.heap.MemoryPool(std.http.Server.Response).init(allocator);
while (true) {
// inside the loop
// get a pointer to a new `Response` struct
var conn = try pool.create();
var aa = std.heap.ArenaAllocator.init(allocator); // will be freed by the spawned thread.
// assign the return value of accept to the memory location pointed to by conn
conn.* = try svr_internal.accept(.{ .allocator = aa.allocator(), .header_strategy = .{ .dynamic = 8192 } });
std.debug.print("server thread {*} listening on {}\n", .{ conn, 8080 });
try tp.spawn(handle, .{ conn, aa, &pool });
}
}
Did I missed something,The address isn’t changed either.
server thread http.Server.Response@27ae44e0010 listening on 8080
server thread http.Server.Response@27ae44f0010 listening on 8080
server thread http.Server.Response@27ae44e0010 listening on 8080
error: request wasn't finished!
server thread http.Server.Response@27ae44f0010 listening on 8080
server thread http.Server.Response@27ae44e0010 listening on 8080
server thread http.Server.Response@27ae44f0010 listening on 8080
You are getting two different addresses that are alternating in the output you posted. When I test this locally I also get a couple different addresses. If one request completes before the next starts, then the address will be re-used; that is what an item pool does.
Thanks for kindly reply.
I have noticed two address is returned and used.
But I would like to one request per thread ,Is that possible?
Or one request per thread is not a usual case.
Each time a request comes in it creates a Response
object in the memory pool and then submits a handle
job with that response to run in the thread pool that you’ve created. Once this job finishes, the Response
is destroyed and its memory location goes back into the memory pool.
So you currently do have one request per thread (up to the max 15 threads that you have in your thread pool). In testing this with many simultaneous requests, it does seem to crash, unless I switch over to just using the general purpose allocator and not using an arena.
Thank you very much.
I did not know the memory location reused this way, I was thought those are just same objects.
I’ll make further test to see if this code working as expected.