-
-
Notifications
You must be signed in to change notification settings - Fork 83
/
users.zig
205 lines (184 loc) · 6.28 KB
/
users.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
const std = @import("std");
alloc: std.mem.Allocator = undefined,
users: std.AutoHashMap(usize, InternalUser) = undefined,
lock: std.Thread.Mutex = undefined,
count: usize = 0,
pub const Self = @This();
const InternalUser = struct {
id: usize = 0,
firstnamebuf: [64]u8,
firstnamelen: usize,
lastnamebuf: [64]u8,
lastnamelen: usize,
};
pub const User = struct {
id: usize = 0,
first_name: []const u8,
last_name: []const u8,
};
pub fn init(a: std.mem.Allocator) Self {
return .{
.alloc = a,
.users = std.AutoHashMap(usize, InternalUser).init(a),
.lock = std.Thread.Mutex{},
};
}
pub fn deinit(self: *Self) void {
self.users.deinit();
}
// the request will be freed (and its mem reused by facilio) when it's
// completed, so we take copies of the names
pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize {
var user: InternalUser = undefined;
user.firstnamelen = 0;
user.lastnamelen = 0;
if (first) |firstname| {
@memcpy(user.firstnamebuf[0..firstname.len], firstname);
user.firstnamelen = firstname.len;
}
if (last) |lastname| {
@memcpy(user.lastnamebuf[0..lastname.len], lastname);
user.lastnamelen = lastname.len;
}
// We lock only on insertion, deletion, and listing
self.lock.lock();
defer self.lock.unlock();
user.id = self.count + 1;
if (self.users.put(user.id, user)) {
self.count += 1;
return user.id;
} else |err| {
std.debug.print("addByName error: {}\n", .{err});
// make sure we pass on the error
return err;
}
}
pub fn delete(self: *Self, id: usize) bool {
// We lock only on insertion, deletion, and listing
self.lock.lock();
defer self.lock.unlock();
const ret = self.users.remove(id);
if (ret) {
self.count -= 1;
}
return ret;
}
pub fn get(self: *Self, id: usize) ?User {
// we don't care about locking here, as our usage-pattern is unlikely to
// get a user by id that is not known yet
if (self.users.getPtr(id)) |pUser| {
return .{
.id = pUser.id,
.first_name = pUser.firstnamebuf[0..pUser.firstnamelen],
.last_name = pUser.lastnamebuf[0..pUser.lastnamelen],
};
}
return null;
}
pub fn update(
self: *Self,
id: usize,
first: ?[]const u8,
last: ?[]const u8,
) bool {
// we don't care about locking here
// we update in-place, via getPtr
if (self.users.getPtr(id)) |pUser| {
pUser.firstnamelen = 0;
pUser.lastnamelen = 0;
if (first) |firstname| {
@memcpy(pUser.firstnamebuf[0..firstname.len], firstname);
pUser.firstnamelen = firstname.len;
}
if (last) |lastname| {
@memcpy(pUser.lastnamebuf[0..lastname.len], lastname);
pUser.lastnamelen = lastname.len;
}
}
return false;
}
pub fn toJSON(self: *Self) ![]const u8 {
self.lock.lock();
defer self.lock.unlock();
// We create a User list that's JSON-friendly
// NOTE: we could also implement the whole JSON writing ourselves here,
// working directly with InternalUser elements of the users hashmap.
// might actually save some memory
// TODO: maybe do it directly with the user.items
var l: std.ArrayList(User) = std.ArrayList(User).init(self.alloc);
defer l.deinit();
// the potential race condition is fixed by jsonifying with the mutex locked
var it = JsonUserIteratorWithRaceCondition.init(&self.users);
while (it.next()) |user| {
try l.append(user);
}
std.debug.assert(self.users.count() == l.items.len);
std.debug.assert(self.count == l.items.len);
return std.json.stringifyAlloc(self.alloc, l.items, .{});
}
//
// Note: the following code is kept in here because it taught us a lesson
//
pub fn listWithRaceCondition(self: *Self, out: *std.ArrayList(User)) !void {
// We lock only on insertion, deletion, and listing
//
// NOTE: race condition:
// =====================
//
// the list returned from here contains elements whose slice fields
// (.first_name and .last_name) point to char buffers of elements of the
// users list:
//
// user.first_name -> internal_user.firstnamebuf[..]
//
// -> we're only referencing the memory of first and last names.
// -> while the caller works with this list, e.g. "slowly" converting it to
// JSON, the users hashmap might be added to massively in the background,
// causing it to GROW -> realloc -> all slices get invalidated!
//
// So, to mitigate that, either:
// - [x] listing and converting to JSON must become one locked operation
// - or: the iterator must make copies of the strings
self.lock.lock();
defer self.lock.unlock();
var it = JsonUserIteratorWithRaceCondition.init(&self.users);
while (it.next()) |user| {
try out.append(user);
}
std.debug.assert(self.users.count() == out.items.len);
std.debug.assert(self.count == out.items.len);
}
const JsonUserIteratorWithRaceCondition = struct {
it: std.AutoHashMap(usize, InternalUser).ValueIterator = undefined,
const This = @This();
// careful:
// - Self refers to the file's struct
// - This refers to the JsonUserIterator struct
pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) This {
return .{
.it = internal_users.valueIterator(),
};
}
pub fn next(this: *This) ?User {
if (this.it.next()) |pUser| {
// we get a pointer to the internal user. so it should be safe to
// create slices from its first and last name buffers
//
// SEE ABOVE NOTE regarding race condition why this is can be problematic
var user: User = .{
// we don't need .* syntax but want to make it obvious
.id = pUser.*.id,
.first_name = pUser.*.firstnamebuf[0..pUser.*.firstnamelen],
.last_name = pUser.*.lastnamebuf[0..pUser.*.lastnamelen],
};
if (pUser.*.firstnamelen == 0) {
user.first_name = "";
}
if (pUser.*.lastnamelen == 0) {
user.last_name = "";
}
return user;
}
return null;
}
};