You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
HTTP servers cache the Date: header line and refresh it once per second (nginx model). The natural vlib API for that is time.push_to_http_header, but its current shape makes an allocation-free cached-Date rebuild impossible:
It can only append to a dynamic []u8 — there is no way to write the 29 bytes into caller-owned storage (a fixed array, or a preallocated region of a dynamic array) at an offset.
Internally it calls weekday_str() and smonth(), which each do a substr (long_days[i][0..3], months_string[i*3..(i+1)*3]) — two heap allocations per call.
The body of push_to_http_header already builds the 29 bytes in a stack template via the pointer-based int_to_ptr_byte_array_no_pad — it is 90% of the needed API. The change moves that body into @[unsafe] write_http_header(dst &u8, dst_len int) ! — pointer-based so one API serves fixed arrays, dynamic arrays and arenas at any offset; bounds-checked (refuses dst_len < time.http_header_len, writing nothing) so a caller cannot silently overrun; @[unsafe]-tagged so the raw-pointer contract is explicit at the call site (like vbytes). push_to_http_header becomes a thin wrapper with unchanged behavior — all 17 vlib/time tests pass, output verified byte-identical to http_header_string() across second/minute/hour/day/leap-year transitions on both storage shapes.
diff --git a/vlib/time/format.v b/vlib/time/format.v
index b914ea0e8..beae94955 100644
--- a/vlib/time/format.v+++ b/vlib/time/format.v@@ -736,24 +736,42 @@ pub fn (t Time) http_header_string() string {
return buf.bytestr()
}
-// push_to_http_header returns a date string in the format used in HTTP headers, as defined in RFC 2616.-// e.g. "Sun, 06 Nov 1994 08:49:37 GMT"-pub fn (t Time) push_to_http_header(mut buffer []u8) {- day_str := t.weekday_str()- month_str := t.smonth()-- mut buf := [day_str[0], day_str[1], day_str[2], `,`, ` `, `0`, `0`, ` `, month_str[0], month_str[1],- month_str[2], ` `, `0`, `0`, `0`, `0`, ` `, `0`, `0`, `:`, `0`, `0`, `:`, `0`, `0`, ` `,- `G`, `M`, `T`]!+// http_header_len is the byte length of an HTTP-date (RFC 9110 IMF-fixdate).+pub const http_header_len = 29++// write_http_header writes the 29-byte HTTP-date ("Sun, 06 Nov 1994 08:49:37 GMT",+// RFC 9110 IMF-fixdate) at dst, which may point into a fixed array or a dynamic+// array's data, at any offset. dst_len is the number of writable bytes at dst;+// an error is returned when it is less than http_header_len, so a caller cannot+// silently overrun its buffer. No allocation on the success path.+@[unsafe]+pub fn (t Time) write_http_header(dst &u8, dst_len int) ! {+ if dst_len < http_header_len {+ return error('time.write_http_header: dst_len must be >= 29')+ }+ day_str := long_days[iclamp(0, t.day_of_week() - 1, 6)] // read in place: no substr+ mi := iclamp(0, t.month - 1, 11) * 3++ mut buf := [day_str[0], day_str[1], day_str[2], `,`, ` `, `0`, `0`, ` `, months_string[mi],+ months_string[mi + 1], months_string[mi + 2], ` `, `0`, `0`, `0`, `0`, ` `, `0`, `0`, `:`,+ `0`, `0`, `:`, `0`, `0`, ` `, `G`, `M`, `T`]!
unsafe {
int_to_ptr_byte_array_no_pad(t.day, &buf[5], 2)
int_to_ptr_byte_array_no_pad(t.year, &buf[12], 4)
int_to_ptr_byte_array_no_pad(t.hour, &buf[17], 2)
int_to_ptr_byte_array_no_pad(t.minute, &buf[20], 2)
int_to_ptr_byte_array_no_pad(t.second, &buf[23], 2)
+ vmemcpy(dst, &buf[0], 29)
}
+}++// push_to_http_header appends the 29-byte HTTP-date to buffer, as defined in RFC 2616.+// e.g. "Sun, 06 Nov 1994 08:49:37 GMT"+pub fn (t Time) push_to_http_header(mut buffer []u8) {+ mut buf := [29]u8{}
unsafe {
- buffer.push_many(&buf[0], buf.len)+ t.write_http_header(&buf[0], 29) or {} // 29 >= http_header_len: cannot fail+ buffer.push_many(&buf[0], 29)
}
}
Optional follow-up
For the cached use case an incremental "update only the digits that rolled over" helper is another ~100x on top (same minute = only the 2 seconds digits change; full calendar reformat only on day rollover). Worked, oracle-tested reference implementation: https://github.com/enghitalo/vanilla/blob/main/examples/async_date_timerfd/main.v
Use Case
Any V HTTP server that sends the RFC 9110-mandated Date header without paying per-request (or even per-second) allocations for it.
Other Information
The substr allocations in weekday_str()/smonth() also affect other formatting paths; the diff above removes them from the HTTP-date path without touching those public functions.
Describe the feature
HTTP servers cache the
Date:header line and refresh it once per second (nginx model). The natural vlib API for that istime.push_to_http_header, but its current shape makes an allocation-free cached-Date rebuild impossible:[]u8— there is no way to write the 29 bytes into caller-owned storage (a fixed array, or a preallocated region of a dynamic array) at an offset.weekday_str()andsmonth(), which each do a substr (long_days[i][0..3],months_string[i*3..(i+1)*3]) — two heap allocations per call.Measurements (current master,
-prod, 1M iterations, x86-64 Linux)line.clear()+ appends +time.utc().push_to_http_header(mut line)time.utc()aloneC.time(0)[37]u8cache, incremental digit update (reference impl below)Proposed Solution — minimal diff, validated (PR #27639)
The body of
push_to_http_headeralready builds the 29 bytes in a stack template via the pointer-basedint_to_ptr_byte_array_no_pad— it is 90% of the needed API. The change moves that body into@[unsafe] write_http_header(dst &u8, dst_len int) !— pointer-based so one API serves fixed arrays, dynamic arrays and arenas at any offset; bounds-checked (refusesdst_len < time.http_header_len, writing nothing) so a caller cannot silently overrun;@[unsafe]-tagged so the raw-pointer contract is explicit at the call site (likevbytes).push_to_http_headerbecomes a thin wrapper with unchanged behavior — all 17vlib/timetests pass, output verified byte-identical tohttp_header_string()across second/minute/hour/day/leap-year transitions on both storage shapes.Optional follow-up
For the cached use case an incremental "update only the digits that rolled over" helper is another ~100x on top (same minute = only the 2 seconds digits change; full calendar reformat only on day rollover). Worked, oracle-tested reference implementation:
https://github.com/enghitalo/vanilla/blob/main/examples/async_date_timerfd/main.v
Use Case
Any V HTTP server that sends the RFC 9110-mandated
Dateheader without paying per-request (or even per-second) allocations for it.Other Information
The substr allocations in
weekday_str()/smonth()also affect other formatting paths; the diff above removes them from the HTTP-date path without touching those public functions.Acknowledgements
Note
You can use the 👍 reaction to increase the issue's priority for developers.
Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.