Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent interleaving calls to HashMap.removeAssertDiscard() / HashMap.putAssumeCapacity() from causing the hashmap to run out of memory #7468

Closed
lithdew opened this issue Dec 16, 2020 · 3 comments
Labels
accepted This proposal is planned. enhancement Solving this issue will likely involve adding new logic or components to the codebase. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. standard library This issue involves writing Zig code for the standard library.
Milestone

Comments

@lithdew
Copy link
Contributor

lithdew commented Dec 16, 2020

The test passes for ArrayHashMap and otherwise causes an integer overflow on the available number of Entry's that may be reclaimed.

Test [1/1] test "HashMapUnmanaged.removeAssertDiscard() / HashMapUnmanaged.putAssumeCapac... integer overflow
/home/kenta/Desktop/zig/lib/std/hash_map.zig:621:32: 0x20b69b in std.hash_map.HashMapUnmanaged(usize,usize,std.hash_map.getAutoHashFn(usize).hash,std.hash_map.getAutoEqlFn(usize).eql,80).getOrPutAssumeCapacity (test)
                self.available -= 1;
                               ^
/home/kenta/Desktop/zig/lib/std/hash_map.zig:475:52: 0x207064 in std.hash_map.HashMapUnmanaged(usize,usize,std.hash_map.getAutoHashFn(usize).hash,std.hash_map.getAutoEqlFn(usize).eql,80).putAssumeCapacity (test)
            const gop = self.getOrPutAssumeCapacity(key);
                                                   ^
/home/kenta/Desktop/sleepy/test.zig:17:30: 0x2068c5 in test "HashMapUnmanaged.removeAssertDiscard() / HashMapUnmanaged.putAssumeCapacity()" (test)
        map.putAssumeCapacity(i, 0);
const std = @import("std");
const testing = std.testing;

test "HashMapUnmanaged.removeAssertDiscard() / HashMapUnmanaged.putAssumeCapacity()" {
    const allocator = testing.allocator;

    var map: std.AutoHashMapUnmanaged(usize, usize) = .{};
    defer map.deinit(allocator);

    try map.ensureCapacity(allocator, 10);

    var i: usize = 0;
    while (i < 20) : (i += 1) {
        if (i >= 10) {
            map.removeAssertDiscard(i - 10);
        }
        map.putAssumeCapacity(i, 0);
    }
}
LemonBoy added a commit to LemonBoy/zig that referenced this issue Dec 17, 2020
Whenever self.available reaches zero we're not allowed to use free
slots, enforce this constraint while looking for a usable one.

Closes ziglang#7468
@Vexu Vexu added bug Observed behavior contradicts documented or intended behavior standard library This issue involves writing Zig code for the standard library. labels Dec 17, 2020
@Vexu Vexu added this to the 0.8.0 milestone Dec 17, 2020
@Sahnvour
Copy link
Contributor

I think this is expected behaviour, and that may expose a flaw in the design or the API (depending on your point of view).
Having deleted items leaving tombstones means that they still participate in the load of the hashmap, hence available is not incremented. Some of your deleted elements may not be reused directly when inserting new ones, so available is decremented in those cases, and eventually drops below 0.

To phrase it another way, tombstones play a role in wether a call to putAssumeCapacity is legal or not.

@andrewrk andrewrk modified the milestones: 0.8.0, 0.8.1 Jun 4, 2021
@andrewrk andrewrk modified the milestones: 0.8.1, 0.9.1 Aug 31, 2021
@andrewrk andrewrk modified the milestones: 0.9.1, 0.9.0 Nov 20, 2021
@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. and removed bug Observed behavior contradicts documented or intended behavior labels Nov 27, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.11.0 Nov 27, 2021
@jorangreef
Copy link
Sponsor Contributor

jorangreef commented Dec 13, 2021

@Sahnvour I believe it's possible for tombstones to be replaced when inserting on the linear probing path? They're only needed when removing to avoid breaking an existing linear probe path (by putting an empty pothole in it) depended on by another linear probe path. However, it's safe to later replace them with other values because that leaves the other linear probe path still intact.

I think that would be the right fix (i.e. don't conflate tombstones and free slots, but resize only when there are no free slots remaining) for these issues and make the API less surprising, especially for embedded use cases where static allocation is required.

@andrewrk andrewrk modified the milestones: 0.11.0, 0.9.0 Dec 17, 2021
@andrewrk andrewrk added accepted This proposal is planned. enhancement Solving this issue will likely involve adding new logic or components to the codebase. labels Dec 17, 2021
@andrewrk andrewrk changed the title Interleaving calls to HashMap.removeAssertDiscard() / HashMap.putAssumeCapacity() causes hashmap to run out of memory. Prevent interleaving calls to HashMap.removeAssertDiscard() / HashMap.putAssumeCapacity() from causing the hashmap to run out of memory Dec 17, 2021
@andrewrk
Copy link
Member

Landed in in ef0566d.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. enhancement Solving this issue will likely involve adding new logic or components to the codebase. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. standard library This issue involves writing Zig code for the standard library.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants