HNHacker News
TopNewBestAskShowJobs

ynik

1,017 karma · joined October 25, 2013

submissionscomments
ynik··on Linux Memory Overcommit (2007)
However Windows has stacks that commit on-demand, which is kinda similar to overcommit. It can result in programs terminating on any random function call (if that call requires allocating a new page of stack). https://learn.microsoft.com/en-us/windows/win32/procthread/t...

However this is very rare to happen in practice -- stack growth isn't very frequent in typical applications; so usually a malloc() will fail first in low-memory situations.

A Windows program can avoid the risk of getting terminated on stack growth by specifying equal commit and reserve sizes for the stack. So in least in theory, it's possible to write a windows program that is reliable in low memory situations.

ynik··on Debugging a rustc segfault on Illumos
But if you're on 64-bit; you can just create the threads with a huge stack size limit (e.g. 1GB) and let the OS handle automatically growing the actual stack size. No need to reinvent the wheel.

Stack size is the one area where Windows does have something like "overcommit" by default, as you can separately configure the reserved and commit sizes of the stack: https://learn.microsoft.com/en-us/windows/win32/procthread/t...

ynik··on Clang vs. Clang
That's no longer true: https://www.intel.com/content/www/us/en/developer/articles/t... x86 hardware does not guarantee anything unless you enable "Data Operand Independent Timing Mode", which is disabled by default and AFAIK can only be enabled by kernel-level code. So unless operating systems grow new syscalls to enable this mode, you're just out of luck on modern hardware.
ynik··on Weekend projects: getting silly with C
But does `printf();` return to the caller unconditionally?

This is far from obvious -- especially once SIGPIPE comes into play, it's quite possible that printf will terminate the program and prevent the undefined behavior from occurring. Which means the compiler is not allowed to optimize it out.

ynik··on How GCC and Clang handle statically known undefined behaviour
But a function blocking indefinitely while repeatedly writing to a volatile variable is well-defined. So the compiler cannot remove a function call followed by UB unless it knows that the function won't do that.

In theory, the compiler could know that since `printf` is a well-known standard function. In practice, `printf` might even exit the program via SIGPIPE, so I don't think any compiler will assume that it definitely will return.

ynik··on How GCC and Clang handle statically known undefined behaviour
That's not true.

If you do `5 / argc`, that's only undefined behavior if your program is called without any arguments; if there are arguments then the behavior is well defined.

Instead, the presence of UB in the execution path that is actually taken, renders invalid the whole execution path (including whatever happens "before" the UB). That is, an execution path has either defined or undefined behavior, it cannot be "defined up to point-in-time T". But other execution paths are independent.

Thus, UB can "time-travel", but only if it would also have occurred without time travel. It must be caused by something happening at runtime in the program on the time-travel-free theoretical abstract machine; it cannot be its own cause (no time travel paradoxes).

So the "time-travel" explanation sounds a lot more scary than it actually is.

ynik··on IPC in Rust – A Ping Pong Comparison
It causes a panic if the write to stdout fails (e.g. if it's a pipe and the other side was closed, or if stdout is redirected to a file and the disk is full). The rust compiler requires handling this potential error in some way, or you get a compiler error. unwrap() "handles" the IO error by crashing the program with a panic, which is the simplest way of avoiding the compiler error. It's fine for such a toy program, but would be inappropriate for production code.
ynik··on Will we have a negative leap second? [pdf] (2022)
Yep. Timezone definitions already change regularly due to governments messing around with time; so software is already set up to handle this case.

Though I wonder what happens with the international dateline -- would it shift around the world, or would the UTC offsets get larger and larger? I imagine the latter would eventually also result in software issues when the UTC offset starts exceeding 24h.

ynik··on Transformers Can Do Arithmetic with the Right Embeddings
I thinks that's far from the only problem. To me the most obvious problem is that we use right-to-left numbers (think about the order you're writing digits when doing long addition) in a left-to-right language. Without a special number-flipping step; the transformer is forced to produce the output token-by-token, i.e. from left-to-right. Without the ability to store additional internal state, this turns addition into an O(N²) problem purely due to the suboptimal output ordering!
ynik··on Reclaiming IPv4 Class E's 240.0.0.0/4
> If anything is trying to route to only 4 octlets, routers add a 1. in front of it. Bam, legacy solved.

Only for unidirectional UDP.

How do you think the other side would be able to respond if it's unaware of the 5th octlet? Basically every protocol in existence requires two-way communication and there's no backward compatible way of providing that.

ynik··on The CompCert C Compiler
You are mistaken there. An array `int arr[10][20][30];` is a single contiguous (stack) allocation.

I recommend you read up on what pointer decay actually does; it's more complicated than replacing all arrays with pointers!

In particular, the type of an `arr` expression (after decay) is `int(*)[20][30]`. Decay only ever changes the top-level (outermost) type! And the type of `&arr` is even `int(*)[10][20][30]` -- using `&` or `sizeof` prevents decay from happening. Pointers to arrays are rarely used because using decay is more idiomatic (and because their syntax is unwieldy), but they still exist and would be safer than using decay.

ynik··on The new APT 3.0 solver
I find it's best to treat this as a case of "cross-compilation to old glibc version". That is, you don't want to just link against an old glibc version, you want a full cross-compilation toolchain where the compiler does not pick up headers from `/usr/include` by default (but instead has its own copy of the system headers), and where libstdc++ is also linked against that old glibc version.

We used https://crosstool-ng.github.io/ to create such a "cross-compiler". Now it doesn't matter which distribution our developers use, the same source code will always turn into the same binary (reproducible builds, yay!) and those binaries will work on older distributions than the developers are using. This allows us to ship dynamically linked linux executables; our linux customers can just unzip + run, same as our windows customers, no need to mess with docker containers.

The downside is that we can't just use a library by `apt install`ing it, everything needs to be built with the cross compilation toolchain.

ynik··on The oceans have broken temperature records every day over the past year
It's expected that a warmer year would be warmer at the majority of points in time. Variance should normally result in there being both warmer and colder points. To be warmer at every point in time, the year needs to be significantly warmer.
ynik··on The Performance Impact of C++'s `final` Keyword
Caution! If you compare across languages like that, not all virtual calls are implemented equally. A C++ virtual call is just a load from a fixed offset in the vtbl followed by an indirect call. This is fairly cheap, on modern CPUs pretty much the same as a non-virtual non-inlined call. A Java/C# interface call involves a lot more stuff, because there's no single fixed vtbl offset that's valid for all classes implementing the interface.
ynik··on What even is a JSON number?
Your software will still "lose" cents if you use integers, for operations such as dividing a bill (e.g. divide by 3), or applying 3% APR in monthly increments.

The goal is not to avoid rounding errors (which would be quite difficult when the true account value can be an irrational number, as with 3% APR compounding monthly), but to have the exact same rounding errors that are prescribed by the accounting practices. Which may vary depending on legislation.

A decimal floating point is usually a better starting point than integers are.

ynik··on XZ backdoor: "It's RCE, not auth bypass, and gated/unreplayable."
This particular approach of hooking would be much harder; but a malicious xz has other options as well. It's already in the code path used by dpkg when unpacking packages for security updates, so it could just modify the sshd binary, or maybe add a rootkit to the next kernel security update.

It seems foolish to change our systems to stop one of the steps the attacker used after their code was already running as root; the attacker can just pick something else; as root they have essentially unlimited options.

ynik··on XZ backdoor: "It's RCE, not auth bypass, and gated/unreplayable."
But don't forget that xz is also used as part of dpkg for unpacking packages. The whole purpose of dpkg is to update critical system packages. Any SELinux policy that protects from a backdoored dpkg/xz installing a rootkit during the next kernel security update; will also prevent installing real kernel security updates.

The particular way of attack in this OpenSSH backdoor can maybe be prevented; but we've got to realize that the attacker already had full root permissions and there's no way of protecting from that.

ynik··on The Myers diff algorithm: part 1
But there's also the not-so-common (yet still somewhat frequent) case where the inputs are highly different. Myers diff alone would take quadratic time, which in practice is often unacceptable.

Real-world implementations often have a limit on the amount of difference (e.g. max(256, sqrt(input size))), and if that's exceeded, they switch to heuristics (so the resulting diff is no longer guaranteed to be minimal). This reduces the worst-case runtime from O(N*2) to O(N*1.5).

ynik··on gh-116167: Allow disabling the GIL
multiprocessing only works fine when you're working on problems that don't require 10+ GB of memory per process. Once you have significant memory usage, you really need to find a way to share that memory across multiple CPU cores. For non-trivial data structures partly implemented in C++ (as optimization, because pure python would be too slow), that means messing with allocators and shared memory. Such GIL-workarounds have easily cost our company several man-years of engineer time, and we still have a bunch of embarrassingly parallel stuff that we still cannot parallelize due to GIL and not yet supporting shared memory allocation for that stuff.

Once the Python ecosystem supports either subinterpreters or nogil, we'll happily migrate to those and get rid of our hacky interprocess code.

Subinterpreters with independent GILs, released with 3.12, theoretically solve our problems but practically are not yet usable, as none of Cython/pybind11/nanobind support them yet. In comparison, nogil feels like it'll be easier to support.

ynik··on Starlink cuts P99 Latency by 60% [pdf]
It's about letting the TCP implementations on the end-points know early that the capacity of the link has been reached; so that they adjust their bandwidth usage before a long queue (which would cause latency) accumulates on the router before the bottleneck link. Active queue management can achieve this with "explicit congestion notification" (ECN, a bit in the IP header) if supported by the endpoints; or alternatively by probabilistically dropping some packets early.

https://en.wikipedia.org/wiki/Active_queue_management

ynik··on Enhancing Factorio with SAT solvers
Belt balancing is necessary when unloading trains, as otherwise uneven consumption can lead to some chests/wagons running out of resources but the train doesn't leave because other wagons are still full.

For mining outputs, it can also be nice to avoid the outpost getting slow/stuck (never able to fill a train) when ore runs out for the outer lanes of miners.

Anywhere else, balancers are unnecessary and just serve to obfuscate a lack of resources.

(But if you really want to play without any balancers at all, that's quite possible: the train problems can also be solved by letting trains leave on a schedule independent of cargo status. All remaining balancers, including those on mining outposts, can be replaced with priority splitters.)

ynik··on The C bounded model checker: criminally underused
The problem is that uninitialized memory really is subtle. After initializing all members of a struct, the padding bytes of that struct are still uninitialized. Yet, it is valid to memcpy the whole struct somewhere else; even with a hand-written char*-based copying loop. (and indeed, this is a good reason for reading uninitialized memory!) So reading uninitialized memory is not always undefined behavior; just most of the time.

The C standard text is barely sufficient for the purposes of compilers, but wholly insufficient for what a model checker would need to know.

ynik··on Unbloating the buffers
The buffer bloat issue happens due to bad queue management on the router immediately preceding the bottleneck link. Whether the queue management happens in "hardware" or "software" depends on the router (but I suspect it'll be software/firmware, not actual hardware).

"bottleneck link" is, of the path from the packet source to the target, whichever link is trying to exceed the available bandwidth. The problematic router (prior to that link) will receive packets faster than it can send them, so it needs to either use the queue, drop the packet, or use the ECN bits in the IP header to tell the target to tell the source to slow down. A router with bad queue management will enqueue packets until the queue is full, before picking one of the other options. This adds latency depending on the queue size; big queues on slow links cause significant latency.

With bad internet connections, the bottleneck link is often the connection between the home router/modem and the ISP's infrastructure. If upload bandwidth is fully used, bad queue management on the home router will cause latency to spike. If download bandwidth is fully used, bad queue management on the ISP's router will cause latency to spike.

With fast internet connections, the bottleneck link might be somewhere else, e.g. at the interface between two ISPs.

Because the problem only happens at the bottleneck link, it is possible to work around a bufferbloat problem in the ISP's infrastructure by introducing another artificial bottleneck in your own home network. So by using a bit of Linux software that artificially limits bandwidth, that software creates a virtual "bottleneck link" where it can control both ends, and thus "fix" the problem (as the link affected by bufferbloat will no longer be overloaded, and thus no longer use its queue). A real fix would require updating the firmware/configuration on the ISP's routers.

ynik··on Tell HN: Hacker News now supports IPv6
Theoretically possible? Yes: https://chown.me/blog/getting-my-own-asn

A good idea? No.

IP addresses are used for routing. Every provider-independent block of addresses needs its own entry in the global routing tables, which are stored on basically every BGP router in the world. There's a limit to the size of these tables, as they need to be stored in special TCAM memory.

Note that IPv4 currently has many more routes than IPv6 not only due to higher adoption, but also due to higher fragmentation: some providers are obtaining many small non-consecutive blocks from different sources (buying addresses wherever they are cheapest), which results in a large number of routing entries. The increase in the overall table size means all other ISPs have to buy new hardware sooner than they would have without the increase caused by fragmentation. Everyone having their own personal address block would cause even worse fragmentation issues.

ynik··on Timeline to remove DSA support in OpenSSH
Missing from your timeline:

   2006-05-01 NIST SP 800-57 Part 1 Rev. 1 is published, which recommends to stop using 1024-bit DSA by 2010.
   2010 passes, but OpenSSH never added support for DSA with keylengths larger than 1024-bit.
   2015-08-11 OpenSSH 7 disables DSA at runtime.
The new announcement is just removing the config option to re-enable the algorithm. Hopefully most people stuck on DSA already starting migrating in 2015 (ideally: finished migration before 2010).
ynik··on Python 3.13 Gets a JIT
The problem is that: * CPython is slow, making extension modules written in C(++) very attractive * The CPython extension API exposes many implementation details * Making use of those implementation details helps those extension modules be even faster

This resulted in a situation where the ecosystem is locked-in to those implementation details: CPython can't change many aspects of its own implementation without breaking the ecosystem; and other implementations are forced to introduce complex and slow emulation layers if they want to be compatible with existing CPython extension modules.

The end result is that alternative implementations are not viable in practice, as most existing libraries don't work without their CPython extension modules -- users of alternative implementations are essentially stuck in their own tiny ecosystem and cannot make use of the large existing (C)Python ecosystem.

CPython at least is in a position where they can push a breaking change to the extension API and most libraries will be forced to adapt. But there's very little incentive for library authors to add separate code paths for other Python implementations, so I don't think other implementations can become viable until CPython cleans up their API.

ynik··on Dennis Ritchie on the priorities of && || vs. == etc. (1982)
& vs && doesn't feel like a problem to me.

& vs == is the real problem: `(val & MASK) == CONSTANT` needs parentheses due to this mistake.

`a & (b == c)` essentially almost never makes sense (== gives you a boolean, and when dealing with booleans you can use && instead), yet that it what you get by default if omitting the parentheses.

ynik··on Dennis Ritchie on the priorities of && || vs. == etc. (1982)
Unfortunately even though this was called out as a mistake in 1982, much more modern languages (e.g. C#) are still copying it.

As painful as breaking changes might be, they beat the alternative of dealing with a bad design indefinitely. At least in more modern languages, the type checker usually catches the mistakes caused by this design mistake.

ynik··on There is an OOM kill count in Linux
That only works if the system is accessing mostly anonymous pages. If the load on the system is accessing plenty of mmapped code/data pages, it can still trash those even if swap is disabled. I've still seen systems hanging for 10+ minutes without recovering even though swap was already disabled.

The Linux kernel OOM killer only acts if there's nothing left that can be discarded, which often happens way too late to save the system. You need a user-mode OOM-killer like earlyoom if you want to keep the system responsive.

ynik··on Improving our safety with a physical quantities and units library
Non-type template parameters (NTTP) were already in the original standard, they've existed since C++ had templates.

Being able to use "auto" for the type of the NTTP was new in C++17; and using class types like std::array for NTTPs was only added in C++20.

← PreviousPage 2 of 8Next →