authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-03 15:57:35-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-03 15:57:35-07:00
log337e1109f5c2894aad9519ee9f7ff1f1f4c65b56
tree222eaf622275f517d143802e616295a760f1e644
parent3b77a845f9a5409d19a9e330b82eece8af4ac18f

std.DoublyLinkedList: remove length tracking

this is trivial to tack on, and in my experience it is rarely wanted.

2 files changed, 110 insertions(+), 99 deletions(-)

lib/std/DoublyLinkedList.zig+66-56
......@@ -17,7 +17,6 @@ const DoublyLinkedList = @This();
1717
1818first: ?*Node = null,
1919last: ?*Node = null,
20len: usize = 0,
2120
2221/// This struct contains only the prev and next pointers and not any data
2322/// payload. The intended usage is to embed it intrusively into another data
......@@ -39,8 +38,6 @@ pub fn insertAfter(list: *DoublyLinkedList, existing_node: *Node, new_node: *Nod
3938 list.last = new_node;
4039 }
4140 existing_node.next = new_node;
42
43 list.len += 1;
4441}
4542
4643pub fn insertBefore(list: *DoublyLinkedList, existing_node: *Node, new_node: *Node) void {
......@@ -55,8 +52,6 @@ pub fn insertBefore(list: *DoublyLinkedList, existing_node: *Node, new_node: *No
5552 list.first = new_node;
5653 }
5754 existing_node.prev = new_node;
58
59 list.len += 1;
6055}
6156
6257/// Concatenate list2 onto the end of list1, removing all entries from the former.
......@@ -69,16 +64,13 @@ pub fn concatByMoving(list1: *DoublyLinkedList, list2: *DoublyLinkedList) void {
6964 if (list1.last) |l1_last| {
7065 l1_last.next = list2.first;
7166 l2_first.prev = list1.last;
72 list1.len += list2.len;
7367 } else {
7468 // list1 was empty
7569 list1.first = list2.first;
76 list1.len = list2.len;
7770 }
7871 list1.last = list2.last;
7972 list2.first = null;
8073 list2.last = null;
81 list2.len = 0;
8274}
8375
8476/// Insert a new node at the end of the list.
......@@ -109,8 +101,6 @@ pub fn prepend(list: *DoublyLinkedList, new_node: *Node) void {
109101 list.last = new_node;
110102 new_node.prev = null;
111103 new_node.next = null;
112
113 list.len = 1;
114104 }
115105}
116106
......@@ -134,9 +124,6 @@ pub fn remove(list: *DoublyLinkedList, node: *Node) void {
134124 // Last element of the list.
135125 list.last = node.prev;
136126 }
137
138 list.len -= 1;
139 assert(list.len == 0 or (list.first != null and list.last != null));
140127}
141128
142129/// Remove and return the last node in the list.
......@@ -159,28 +146,43 @@ pub fn popFirst(list: *DoublyLinkedList) ?*Node {
159146 return first;
160147}
161148
162test "basic DoublyLinkedList test" {
163 const L = DoublyLinkedList(u32);
164 var list = L{};
165
166 var one = L.Node{ .data = 1 };
167 var two = L.Node{ .data = 2 };
168 var three = L.Node{ .data = 3 };
169 var four = L.Node{ .data = 4 };
170 var five = L.Node{ .data = 5 };
149/// Iterate over all nodes, returning the count.
150///
151/// This operation is O(N). Consider tracking the length separately rather than
152/// computing it.
153pub fn len(list: DoublyLinkedList) usize {
154 var count: usize = 0;
155 var it: ?*const Node = list.first;
156 while (it) |n| : (it = n.next) count += 1;
157 return count;
158}
171159
172 list.append(&two); // {2}
173 list.append(&five); // {2, 5}
174 list.prepend(&one); // {1, 2, 5}
175 list.insertBefore(&five, &four); // {1, 2, 4, 5}
176 list.insertAfter(&two, &three); // {1, 2, 3, 4, 5}
160test "basics" {
161 const L = struct {
162 data: u32,
163 node: DoublyLinkedList.Node = .{},
164 };
165 var list: DoublyLinkedList = .{};
166
167 var one: L = .{ .data = 1 };
168 var two: L = .{ .data = 2 };
169 var three: L = .{ .data = 3 };
170 var four: L = .{ .data = 4 };
171 var five: L = .{ .data = 5 };
172
173 list.append(&two.node); // {2}
174 list.append(&five.node); // {2, 5}
175 list.prepend(&one.node); // {1, 2, 5}
176 list.insertBefore(&five.node, &four.node); // {1, 2, 4, 5}
177 list.insertAfter(&two.node, &three.node); // {1, 2, 3, 4, 5}
177178
178179 // Traverse forwards.
179180 {
180181 var it = list.first;
181182 var index: u32 = 1;
182183 while (it) |node| : (it = node.next) {
183 try testing.expect(node.data == index);
184 const l: *L = @fieldParentPtr("node", node);
185 try testing.expect(l.data == index);
184186 index += 1;
185187 }
186188 }
......@@ -190,51 +192,56 @@ test "basic DoublyLinkedList test" {
190192 var it = list.last;
191193 var index: u32 = 1;
192194 while (it) |node| : (it = node.prev) {
193 try testing.expect(node.data == (6 - index));
195 const l: *L = @fieldParentPtr("node", node);
196 try testing.expect(l.data == (6 - index));
194197 index += 1;
195198 }
196199 }
197200
198201 _ = list.popFirst(); // {2, 3, 4, 5}
199202 _ = list.pop(); // {2, 3, 4}
200 list.remove(&three); // {2, 4}
203 list.remove(&three.node); // {2, 4}
201204
202 try testing.expect(list.first.?.data == 2);
203 try testing.expect(list.last.?.data == 4);
204 try testing.expect(list.len == 2);
205 try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 2);
206 try testing.expect(@as(*L, @fieldParentPtr("node", list.last.?)).data == 4);
207 try testing.expect(list.len() == 2);
205208}
206209
207test "DoublyLinkedList concatenation" {
208 const L = DoublyLinkedList(u32);
209 var list1 = L{};
210 var list2 = L{};
211
212 var one = L.Node{ .data = 1 };
213 var two = L.Node{ .data = 2 };
214 var three = L.Node{ .data = 3 };
215 var four = L.Node{ .data = 4 };
216 var five = L.Node{ .data = 5 };
217
218 list1.append(&one);
219 list1.append(&two);
220 list2.append(&three);
221 list2.append(&four);
222 list2.append(&five);
210test "concatenation" {
211 const L = struct {
212 data: u32,
213 node: DoublyLinkedList.Node = .{},
214 };
215 var list1: DoublyLinkedList = .{};
216 var list2: DoublyLinkedList = .{};
217
218 var one: L = .{ .data = 1 };
219 var two: L = .{ .data = 2 };
220 var three: L = .{ .data = 3 };
221 var four: L = .{ .data = 4 };
222 var five: L = .{ .data = 5 };
223
224 list1.append(&one.node);
225 list1.append(&two.node);
226 list2.append(&three.node);
227 list2.append(&four.node);
228 list2.append(&five.node);
223229
224230 list1.concatByMoving(&list2);
225231
226 try testing.expect(list1.last == &five);
227 try testing.expect(list1.len == 5);
232 try testing.expect(list1.last == &five.node);
233 try testing.expect(list1.len() == 5);
228234 try testing.expect(list2.first == null);
229235 try testing.expect(list2.last == null);
230 try testing.expect(list2.len == 0);
236 try testing.expect(list2.len() == 0);
231237
232238 // Traverse forwards.
233239 {
234240 var it = list1.first;
235241 var index: u32 = 1;
236242 while (it) |node| : (it = node.next) {
237 try testing.expect(node.data == index);
243 const l: *L = @fieldParentPtr("node", node);
244 try testing.expect(l.data == index);
238245 index += 1;
239246 }
240247 }
......@@ -244,7 +251,8 @@ test "DoublyLinkedList concatenation" {
244251 var it = list1.last;
245252 var index: u32 = 1;
246253 while (it) |node| : (it = node.prev) {
247 try testing.expect(node.data == (6 - index));
254 const l: *L = @fieldParentPtr("node", node);
255 try testing.expect(l.data == (6 - index));
248256 index += 1;
249257 }
250258 }
......@@ -257,7 +265,8 @@ test "DoublyLinkedList concatenation" {
257265 var it = list2.first;
258266 var index: u32 = 1;
259267 while (it) |node| : (it = node.next) {
260 try testing.expect(node.data == index);
268 const l: *L = @fieldParentPtr("node", node);
269 try testing.expect(l.data == index);
261270 index += 1;
262271 }
263272 }
......@@ -267,7 +276,8 @@ test "DoublyLinkedList concatenation" {
267276 var it = list2.last;
268277 var index: u32 = 1;
269278 while (it) |node| : (it = node.prev) {
270 try testing.expect(node.data == (6 - index));
279 const l: *L = @fieldParentPtr("node", node);
280 try testing.expect(l.data == (6 - index));
271281 index += 1;
272282 }
273283 }
lib/std/http/Client.zig+44-43
......@@ -46,9 +46,9 @@ https_proxy: ?*Proxy = null,
4646pub const ConnectionPool = struct {
4747 mutex: std.Thread.Mutex = .{},
4848 /// Open connections that are currently in use.
49 used: Queue = .{},
49 used: std.DoublyLinkedList = .{},
5050 /// Open connections that are not currently in use.
51 free: Queue = .{},
51 free: std.DoublyLinkedList = .{},
5252 free_len: usize = 0,
5353 free_size: usize = 32,
5454
......@@ -59,9 +59,6 @@ pub const ConnectionPool = struct {
5959 protocol: Connection.Protocol,
6060 };
6161
62 const Queue = std.DoublyLinkedList(Connection);
63 pub const Node = Queue.Node;
64
6562 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.
6663 /// If no connection is found, null is returned.
6764 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {
......@@ -70,33 +67,34 @@ pub const ConnectionPool = struct {
7067
7168 var next = pool.free.last;
7269 while (next) |node| : (next = node.prev) {
73 if (node.data.protocol != criteria.protocol) continue;
74 if (node.data.port != criteria.port) continue;
70 const connection: *Connection = @fieldParentPtr("pool_node", node);
71 if (connection.protocol != criteria.protocol) continue;
72 if (connection.port != criteria.port) continue;
7573
7674 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
77 if (!std.ascii.eqlIgnoreCase(node.data.host, criteria.host)) continue;
75 if (!std.ascii.eqlIgnoreCase(connection.host, criteria.host)) continue;
7876
79 pool.acquireUnsafe(node);
80 return &node.data;
77 pool.acquireUnsafe(connection);
78 return connection;
8179 }
8280
8381 return null;
8482 }
8583
8684 /// Acquires an existing connection from the connection pool. This function is not threadsafe.
87 pub fn acquireUnsafe(pool: *ConnectionPool, node: *Node) void {
88 pool.free.remove(node);
85 pub fn acquireUnsafe(pool: *ConnectionPool, connection: *Connection) void {
86 pool.free.remove(&connection.pool_node);
8987 pool.free_len -= 1;
9088
91 pool.used.append(node);
89 pool.used.append(&connection.pool_node);
9290 }
9391
9492 /// Acquires an existing connection from the connection pool. This function is threadsafe.
95 pub fn acquire(pool: *ConnectionPool, node: *Node) void {
93 pub fn acquire(pool: *ConnectionPool, connection: *Connection) void {
9694 pool.mutex.lock();
9795 defer pool.mutex.unlock();
9896
99 return pool.acquireUnsafe(node);
97 return pool.acquireUnsafe(connection);
10098 }
10199
102100 /// Tries to release a connection back to the connection pool. This function is threadsafe.
......@@ -108,38 +106,37 @@ pub const ConnectionPool = struct {
108106 pool.mutex.lock();
109107 defer pool.mutex.unlock();
110108
111 const node: *Node = @fieldParentPtr("data", connection);
112
113 pool.used.remove(node);
109 pool.used.remove(&connection.pool_node);
114110
115 if (node.data.closing or pool.free_size == 0) {
116 node.data.close(allocator);
117 return allocator.destroy(node);
111 if (connection.closing or pool.free_size == 0) {
112 connection.close(allocator);
113 return allocator.destroy(connection);
118114 }
119115
120116 if (pool.free_len >= pool.free_size) {
121 const popped = pool.free.popFirst() orelse unreachable;
117 const popped: *Connection = @fieldParentPtr("pool_node", pool.free.popFirst().?);
122118 pool.free_len -= 1;
123119
124 popped.data.close(allocator);
120 popped.close(allocator);
125121 allocator.destroy(popped);
126122 }
127123
128 if (node.data.proxied) {
129 pool.free.prepend(node); // proxied connections go to the end of the queue, always try direct connections first
124 if (connection.proxied) {
125 // proxied connections go to the end of the queue, always try direct connections first
126 pool.free.prepend(&connection.pool_node);
130127 } else {
131 pool.free.append(node);
128 pool.free.append(&connection.pool_node);
132129 }
133130
134131 pool.free_len += 1;
135132 }
136133
137134 /// Adds a newly created node to the pool of used connections. This function is threadsafe.
138 pub fn addUsed(pool: *ConnectionPool, node: *Node) void {
135 pub fn addUsed(pool: *ConnectionPool, connection: *Connection) void {
139136 pool.mutex.lock();
140137 defer pool.mutex.unlock();
141138
142 pool.used.append(node);
139 pool.used.append(&connection.pool_node);
143140 }
144141
145142 /// Resizes the connection pool. This function is threadsafe.
......@@ -170,18 +167,18 @@ pub const ConnectionPool = struct {
170167
171168 var next = pool.free.first;
172169 while (next) |node| {
173 defer allocator.destroy(node);
170 const connection: *Connection = @fieldParentPtr("pool_node", node);
174171 next = node.next;
175
176 node.data.close(allocator);
172 connection.close(allocator);
173 allocator.destroy(connection);
177174 }
178175
179176 next = pool.used.first;
180177 while (next) |node| {
181 defer allocator.destroy(node);
178 const connection: *Connection = @fieldParentPtr("pool_node", node);
182179 next = node.next;
183
184 node.data.close(allocator);
180 connection.close(allocator);
181 allocator.destroy(node);
185182 }
186183
187184 pool.* = undefined;
......@@ -194,6 +191,9 @@ pub const Connection = struct {
194191 /// undefined unless protocol is tls.
195192 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
196193
194 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
195 pool_node: std.DoublyLinkedList.Node,
196
197197 /// The protocol that this connection is using.
198198 protocol: Protocol,
199199
......@@ -1326,9 +1326,8 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13261326 if (disable_tls and protocol == .tls)
13271327 return error.TlsInitializationFailed;
13281328
1329 const conn = try client.allocator.create(ConnectionPool.Node);
1329 const conn = try client.allocator.create(Connection);
13301330 errdefer client.allocator.destroy(conn);
1331 conn.* = .{ .data = undefined };
13321331
13331332 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
13341333 error.ConnectionRefused => return error.ConnectionRefused,
......@@ -1343,21 +1342,23 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13431342 };
13441343 errdefer stream.close();
13451344
1346 conn.data = .{
1345 conn.* = .{
13471346 .stream = stream,
13481347 .tls_client = undefined,
13491348
13501349 .protocol = protocol,
13511350 .host = try client.allocator.dupe(u8, host),
13521351 .port = port,
1352
1353 .pool_node = .{},
13531354 };
1354 errdefer client.allocator.free(conn.data.host);
1355 errdefer client.allocator.free(conn.host);
13551356
13561357 if (protocol == .tls) {
13571358 if (disable_tls) unreachable;
13581359
1359 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
1360 errdefer client.allocator.destroy(conn.data.tls_client);
1360 conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
1361 errdefer client.allocator.destroy(conn.tls_client);
13611362
13621363 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
13631364 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
......@@ -1375,19 +1376,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
13751376 } else null;
13761377 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
13771378
1378 conn.data.tls_client.* = std.crypto.tls.Client.init(stream, .{
1379 conn.tls_client.* = std.crypto.tls.Client.init(stream, .{
13791380 .host = .{ .explicit = host },
13801381 .ca = .{ .bundle = client.ca_bundle },
13811382 .ssl_key_log_file = ssl_key_log_file,
13821383 }) catch return error.TlsInitializationFailed;
13831384 // This is appropriate for HTTPS because the HTTP headers contain
13841385 // the content length which is used to detect truncation attacks.
1385 conn.data.tls_client.allow_truncation_attacks = true;
1386 conn.tls_client.allow_truncation_attacks = true;
13861387 }
13871388
13881389 client.connection_pool.addUsed(conn);
13891390
1390 return &conn.data;
1391 return conn;
13911392}
13921393
13931394pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;