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();...@@ -17,7 +17,6 @@ const DoublyLinkedList = @This();
1717
18first: ?*Node = null,18first: ?*Node = null,
19last: ?*Node = null,19last: ?*Node = null,
20len: usize = 0,
2120
22/// This struct contains only the prev and next pointers and not any data21/// This struct contains only the prev and next pointers and not any data
23/// payload. The intended usage is to embed it intrusively into another data22/// 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...@@ -39,8 +38,6 @@ pub fn insertAfter(list: *DoublyLinkedList, existing_node: *Node, new_node: *Nod
39 list.last = new_node;38 list.last = new_node;
40 }39 }
41 existing_node.next = new_node;40 existing_node.next = new_node;
42
43 list.len += 1;
44}41}
4542
46pub fn insertBefore(list: *DoublyLinkedList, existing_node: *Node, new_node: *Node) void {43pub 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...@@ -55,8 +52,6 @@ pub fn insertBefore(list: *DoublyLinkedList, existing_node: *Node, new_node: *No
55 list.first = new_node;52 list.first = new_node;
56 }53 }
57 existing_node.prev = new_node;54 existing_node.prev = new_node;
58
59 list.len += 1;
60}55}
6156
62/// Concatenate list2 onto the end of list1, removing all entries from the former.57/// 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 {...@@ -69,16 +64,13 @@ pub fn concatByMoving(list1: *DoublyLinkedList, list2: *DoublyLinkedList) void {
69 if (list1.last) |l1_last| {64 if (list1.last) |l1_last| {
70 l1_last.next = list2.first;65 l1_last.next = list2.first;
71 l2_first.prev = list1.last;66 l2_first.prev = list1.last;
72 list1.len += list2.len;
73 } else {67 } else {
74 // list1 was empty68 // list1 was empty
75 list1.first = list2.first;69 list1.first = list2.first;
76 list1.len = list2.len;
77 }70 }
78 list1.last = list2.last;71 list1.last = list2.last;
79 list2.first = null;72 list2.first = null;
80 list2.last = null;73 list2.last = null;
81 list2.len = 0;
82}74}
8375
84/// Insert a new node at the end of the list.76/// Insert a new node at the end of the list.
...@@ -109,8 +101,6 @@ pub fn prepend(list: *DoublyLinkedList, new_node: *Node) void {...@@ -109,8 +101,6 @@ pub fn prepend(list: *DoublyLinkedList, new_node: *Node) void {
109 list.last = new_node;101 list.last = new_node;
110 new_node.prev = null;102 new_node.prev = null;
111 new_node.next = null;103 new_node.next = null;
112
113 list.len = 1;
114 }104 }
115}105}
116106
...@@ -134,9 +124,6 @@ pub fn remove(list: *DoublyLinkedList, node: *Node) void {...@@ -134,9 +124,6 @@ pub fn remove(list: *DoublyLinkedList, node: *Node) void {
134 // Last element of the list.124 // Last element of the list.
135 list.last = node.prev;125 list.last = node.prev;
136 }126 }
137
138 list.len -= 1;
139 assert(list.len == 0 or (list.first != null and list.last != null));
140}127}
141128
142/// Remove and return the last node in the list.129/// Remove and return the last node in the list.
...@@ -159,28 +146,43 @@ pub fn popFirst(list: *DoublyLinkedList) ?*Node {...@@ -159,28 +146,43 @@ pub fn popFirst(list: *DoublyLinkedList) ?*Node {
159 return first;146 return first;
160}147}
161148
162test "basic DoublyLinkedList test" {149/// Iterate over all nodes, returning the count.
163 const L = DoublyLinkedList(u32);150///
164 var list = L{};151/// This operation is O(N). Consider tracking the length separately rather than
165152/// computing it.
166 var one = L.Node{ .data = 1 };153pub fn len(list: DoublyLinkedList) usize {
167 var two = L.Node{ .data = 2 };154 var count: usize = 0;
168 var three = L.Node{ .data = 3 };155 var it: ?*const Node = list.first;
169 var four = L.Node{ .data = 4 };156 while (it) |n| : (it = n.next) count += 1;
170 var five = L.Node{ .data = 5 };157 return count;
158}
171159
172 list.append(&two); // {2}160test "basics" {
173 list.append(&five); // {2, 5}161 const L = struct {
174 list.prepend(&one); // {1, 2, 5}162 data: u32,
175 list.insertBefore(&five, &four); // {1, 2, 4, 5}163 node: DoublyLinkedList.Node = .{},
176 list.insertAfter(&two, &three); // {1, 2, 3, 4, 5}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
178 // Traverse forwards.179 // Traverse forwards.
179 {180 {
180 var it = list.first;181 var it = list.first;
181 var index: u32 = 1;182 var index: u32 = 1;
182 while (it) |node| : (it = node.next) {183 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);
184 index += 1;186 index += 1;
185 }187 }
186 }188 }
...@@ -190,51 +192,56 @@ test "basic DoublyLinkedList test" {...@@ -190,51 +192,56 @@ test "basic DoublyLinkedList test" {
190 var it = list.last;192 var it = list.last;
191 var index: u32 = 1;193 var index: u32 = 1;
192 while (it) |node| : (it = node.prev) {194 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));
194 index += 1;197 index += 1;
195 }198 }
196 }199 }
197200
198 _ = list.popFirst(); // {2, 3, 4, 5}201 _ = list.popFirst(); // {2, 3, 4, 5}
199 _ = list.pop(); // {2, 3, 4}202 _ = 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);205 try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 2);
203 try testing.expect(list.last.?.data == 4);206 try testing.expect(@as(*L, @fieldParentPtr("node", list.last.?)).data == 4);
204 try testing.expect(list.len == 2);207 try testing.expect(list.len() == 2);
205}208}
206209
207test "DoublyLinkedList concatenation" {210test "concatenation" {
208 const L = DoublyLinkedList(u32);211 const L = struct {
209 var list1 = L{};212 data: u32,
210 var list2 = L{};213 node: DoublyLinkedList.Node = .{},
211214 };
212 var one = L.Node{ .data = 1 };215 var list1: DoublyLinkedList = .{};
213 var two = L.Node{ .data = 2 };216 var list2: DoublyLinkedList = .{};
214 var three = L.Node{ .data = 3 };217
215 var four = L.Node{ .data = 4 };218 var one: L = .{ .data = 1 };
216 var five = L.Node{ .data = 5 };219 var two: L = .{ .data = 2 };
217220 var three: L = .{ .data = 3 };
218 list1.append(&one);221 var four: L = .{ .data = 4 };
219 list1.append(&two);222 var five: L = .{ .data = 5 };
220 list2.append(&three);223
221 list2.append(&four);224 list1.append(&one.node);
222 list2.append(&five);225 list1.append(&two.node);
226 list2.append(&three.node);
227 list2.append(&four.node);
228 list2.append(&five.node);
223229
224 list1.concatByMoving(&list2);230 list1.concatByMoving(&list2);
225231
226 try testing.expect(list1.last == &five);232 try testing.expect(list1.last == &five.node);
227 try testing.expect(list1.len == 5);233 try testing.expect(list1.len() == 5);
228 try testing.expect(list2.first == null);234 try testing.expect(list2.first == null);
229 try testing.expect(list2.last == null);235 try testing.expect(list2.last == null);
230 try testing.expect(list2.len == 0);236 try testing.expect(list2.len() == 0);
231237
232 // Traverse forwards.238 // Traverse forwards.
233 {239 {
234 var it = list1.first;240 var it = list1.first;
235 var index: u32 = 1;241 var index: u32 = 1;
236 while (it) |node| : (it = node.next) {242 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);
238 index += 1;245 index += 1;
239 }246 }
240 }247 }
...@@ -244,7 +251,8 @@ test "DoublyLinkedList concatenation" {...@@ -244,7 +251,8 @@ test "DoublyLinkedList concatenation" {
244 var it = list1.last;251 var it = list1.last;
245 var index: u32 = 1;252 var index: u32 = 1;
246 while (it) |node| : (it = node.prev) {253 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));
248 index += 1;256 index += 1;
249 }257 }
250 }258 }
...@@ -257,7 +265,8 @@ test "DoublyLinkedList concatenation" {...@@ -257,7 +265,8 @@ test "DoublyLinkedList concatenation" {
257 var it = list2.first;265 var it = list2.first;
258 var index: u32 = 1;266 var index: u32 = 1;
259 while (it) |node| : (it = node.next) {267 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);
261 index += 1;270 index += 1;
262 }271 }
263 }272 }
...@@ -267,7 +276,8 @@ test "DoublyLinkedList concatenation" {...@@ -267,7 +276,8 @@ test "DoublyLinkedList concatenation" {
267 var it = list2.last;276 var it = list2.last;
268 var index: u32 = 1;277 var index: u32 = 1;
269 while (it) |node| : (it = node.prev) {278 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));
271 index += 1;281 index += 1;
272 }282 }
273 }283 }
lib/std/http/Client.zig+44-43
...@@ -46,9 +46,9 @@ https_proxy: ?*Proxy = null,...@@ -46,9 +46,9 @@ https_proxy: ?*Proxy = null,
46pub const ConnectionPool = struct {46pub const ConnectionPool = struct {
47 mutex: std.Thread.Mutex = .{},47 mutex: std.Thread.Mutex = .{},
48 /// Open connections that are currently in use.48 /// Open connections that are currently in use.
49 used: Queue = .{},49 used: std.DoublyLinkedList = .{},
50 /// Open connections that are not currently in use.50 /// Open connections that are not currently in use.
51 free: Queue = .{},51 free: std.DoublyLinkedList = .{},
52 free_len: usize = 0,52 free_len: usize = 0,
53 free_size: usize = 32,53 free_size: usize = 32,
5454
...@@ -59,9 +59,6 @@ pub const ConnectionPool = struct {...@@ -59,9 +59,6 @@ pub const ConnectionPool = struct {
59 protocol: Connection.Protocol,59 protocol: Connection.Protocol,
60 };60 };
6161
62 const Queue = std.DoublyLinkedList(Connection);
63 pub const Node = Queue.Node;
64
65 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.62 /// Finds and acquires a connection from the connection pool matching the criteria. This function is threadsafe.
66 /// If no connection is found, null is returned.63 /// If no connection is found, null is returned.
67 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {64 pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection {
...@@ -70,33 +67,34 @@ pub const ConnectionPool = struct {...@@ -70,33 +67,34 @@ pub const ConnectionPool = struct {
7067
71 var next = pool.free.last;68 var next = pool.free.last;
72 while (next) |node| : (next = node.prev) {69 while (next) |node| : (next = node.prev) {
73 if (node.data.protocol != criteria.protocol) continue;70 const connection: *Connection = @fieldParentPtr("pool_node", node);
74 if (node.data.port != criteria.port) continue;71 if (connection.protocol != criteria.protocol) continue;
72 if (connection.port != criteria.port) continue;
7573
76 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)74 // 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);77 pool.acquireUnsafe(connection);
80 return &node.data;78 return connection;
81 }79 }
8280
83 return null;81 return null;
84 }82 }
8583
86 /// Acquires an existing connection from the connection pool. This function is not threadsafe.84 /// Acquires an existing connection from the connection pool. This function is not threadsafe.
87 pub fn acquireUnsafe(pool: *ConnectionPool, node: *Node) void {85 pub fn acquireUnsafe(pool: *ConnectionPool, connection: *Connection) void {
88 pool.free.remove(node);86 pool.free.remove(&connection.pool_node);
89 pool.free_len -= 1;87 pool.free_len -= 1;
9088
91 pool.used.append(node);89 pool.used.append(&connection.pool_node);
92 }90 }
9391
94 /// Acquires an existing connection from the connection pool. This function is threadsafe.92 /// 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 {
96 pool.mutex.lock();94 pool.mutex.lock();
97 defer pool.mutex.unlock();95 defer pool.mutex.unlock();
9896
99 return pool.acquireUnsafe(node);97 return pool.acquireUnsafe(connection);
100 }98 }
10199
102 /// Tries to release a connection back to the connection pool. This function is threadsafe.100 /// Tries to release a connection back to the connection pool. This function is threadsafe.
...@@ -108,38 +106,37 @@ pub const ConnectionPool = struct {...@@ -108,38 +106,37 @@ pub const ConnectionPool = struct {
108 pool.mutex.lock();106 pool.mutex.lock();
109 defer pool.mutex.unlock();107 defer pool.mutex.unlock();
110108
111 const node: *Node = @fieldParentPtr("data", connection);109 pool.used.remove(&connection.pool_node);
112
113 pool.used.remove(node);
114110
115 if (node.data.closing or pool.free_size == 0) {111 if (connection.closing or pool.free_size == 0) {
116 node.data.close(allocator);112 connection.close(allocator);
117 return allocator.destroy(node);113 return allocator.destroy(connection);
118 }114 }
119115
120 if (pool.free_len >= pool.free_size) {116 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().?);
122 pool.free_len -= 1;118 pool.free_len -= 1;
123119
124 popped.data.close(allocator);120 popped.close(allocator);
125 allocator.destroy(popped);121 allocator.destroy(popped);
126 }122 }
127123
128 if (node.data.proxied) {124 if (connection.proxied) {
129 pool.free.prepend(node); // proxied connections go to the end of the queue, always try direct connections first125 // proxied connections go to the end of the queue, always try direct connections first
126 pool.free.prepend(&connection.pool_node);
130 } else {127 } else {
131 pool.free.append(node);128 pool.free.append(&connection.pool_node);
132 }129 }
133130
134 pool.free_len += 1;131 pool.free_len += 1;
135 }132 }
136133
137 /// Adds a newly created node to the pool of used connections. This function is threadsafe.134 /// 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 {
139 pool.mutex.lock();136 pool.mutex.lock();
140 defer pool.mutex.unlock();137 defer pool.mutex.unlock();
141138
142 pool.used.append(node);139 pool.used.append(&connection.pool_node);
143 }140 }
144141
145 /// Resizes the connection pool. This function is threadsafe.142 /// Resizes the connection pool. This function is threadsafe.
...@@ -170,18 +167,18 @@ pub const ConnectionPool = struct {...@@ -170,18 +167,18 @@ pub const ConnectionPool = struct {
170167
171 var next = pool.free.first;168 var next = pool.free.first;
172 while (next) |node| {169 while (next) |node| {
173 defer allocator.destroy(node);170 const connection: *Connection = @fieldParentPtr("pool_node", node);
174 next = node.next;171 next = node.next;
175172 connection.close(allocator);
176 node.data.close(allocator);173 allocator.destroy(connection);
177 }174 }
178175
179 next = pool.used.first;176 next = pool.used.first;
180 while (next) |node| {177 while (next) |node| {
181 defer allocator.destroy(node);178 const connection: *Connection = @fieldParentPtr("pool_node", node);
182 next = node.next;179 next = node.next;
183180 connection.close(allocator);
184 node.data.close(allocator);181 allocator.destroy(node);
185 }182 }
186183
187 pool.* = undefined;184 pool.* = undefined;
...@@ -194,6 +191,9 @@ pub const Connection = struct {...@@ -194,6 +191,9 @@ pub const Connection = struct {
194 /// undefined unless protocol is tls.191 /// undefined unless protocol is tls.
195 tls_client: if (!disable_tls) *std.crypto.tls.Client else void,192 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
197 /// The protocol that this connection is using.197 /// The protocol that this connection is using.
198 protocol: Protocol,198 protocol: Protocol,
199199
...@@ -1326,9 +1326,8 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1326,9 +1326,8 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1326 if (disable_tls and protocol == .tls)1326 if (disable_tls and protocol == .tls)
1327 return error.TlsInitializationFailed;1327 return error.TlsInitializationFailed;
13281328
1329 const conn = try client.allocator.create(ConnectionPool.Node);1329 const conn = try client.allocator.create(Connection);
1330 errdefer client.allocator.destroy(conn);1330 errdefer client.allocator.destroy(conn);
1331 conn.* = .{ .data = undefined };
13321331
1333 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {1332 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
1334 error.ConnectionRefused => return error.ConnectionRefused,1333 error.ConnectionRefused => return error.ConnectionRefused,
...@@ -1343,21 +1342,23 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec...@@ -1343,21 +1342,23 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1343 };1342 };
1344 errdefer stream.close();1343 errdefer stream.close();
13451344
1346 conn.data = .{1345 conn.* = .{
1347 .stream = stream,1346 .stream = stream,
1348 .tls_client = undefined,1347 .tls_client = undefined,
13491348
1350 .protocol = protocol,1349 .protocol = protocol,
1351 .host = try client.allocator.dupe(u8, host),1350 .host = try client.allocator.dupe(u8, host),
1352 .port = port,1351 .port = port,
1352
1353 .pool_node = .{},
1353 };1354 };
1354 errdefer client.allocator.free(conn.data.host);1355 errdefer client.allocator.free(conn.host);
13551356
1356 if (protocol == .tls) {1357 if (protocol == .tls) {
1357 if (disable_tls) unreachable;1358 if (disable_tls) unreachable;
13581359
1359 conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);1360 conn.tls_client = try client.allocator.create(std.crypto.tls.Client);
1360 errdefer client.allocator.destroy(conn.data.tls_client);1361 errdefer client.allocator.destroy(conn.tls_client);
13611362
1362 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {1363 const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
1363 const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {1364 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...@@ -1375,19 +1376,19 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
1375 } else null;1376 } else null;
1376 errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();1377 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, .{
1379 .host = .{ .explicit = host },1380 .host = .{ .explicit = host },
1380 .ca = .{ .bundle = client.ca_bundle },1381 .ca = .{ .bundle = client.ca_bundle },
1381 .ssl_key_log_file = ssl_key_log_file,1382 .ssl_key_log_file = ssl_key_log_file,
1382 }) catch return error.TlsInitializationFailed;1383 }) catch return error.TlsInitializationFailed;
1383 // This is appropriate for HTTPS because the HTTP headers contain1384 // This is appropriate for HTTPS because the HTTP headers contain
1384 // the content length which is used to detect truncation attacks.1385 // 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;
1386 }1387 }
13871388
1388 client.connection_pool.addUsed(conn);1389 client.connection_pool.addUsed(conn);
13891390
1390 return &conn.data;1391 return conn;
1391}1392}
13921393
1393pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;1394pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{NameTooLong} || std.posix.ConnectError;