tangled
alpha
login
or
join now
smokesignal.events
/
quickdid
50
fork
atom
QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.
50
fork
atom
overview
issues
pulls
pipelines
refactor: reducing log level for soft lookup failures
Nick Gerakines
6 months ago
53bc69a7
36286bc3
+98
-13
1 changed file
expand all
collapse all
unified
split
src
handle_resolver_task.rs
+98
-13
src/handle_resolver_task.rs
···
44
44
pub total_succeeded: std::sync::atomic::AtomicU64,
45
45
pub total_failed: std::sync::atomic::AtomicU64,
46
46
pub total_cached: std::sync::atomic::AtomicU64,
47
47
+
pub total_not_found: std::sync::atomic::AtomicU64,
47
48
}
48
49
49
50
/// Handle resolver task processor
···
140
141
.metrics
141
142
.total_cached
142
143
.load(std::sync::atomic::Ordering::Relaxed),
144
144
+
total_not_found = self
145
145
+
.metrics
146
146
+
.total_not_found
147
147
+
.load(std::sync::atomic::Ordering::Relaxed),
143
148
"Handle resolver task processor stopped"
144
149
);
145
150
146
151
Ok(())
152
152
+
}
153
153
+
154
154
+
/// Check if an error represents a soft failure (handle not found)
155
155
+
/// rather than a real error condition.
156
156
+
///
157
157
+
/// These atproto_identity library errors indicate the handle doesn't support
158
158
+
/// the specific resolution method, which is normal and expected:
159
159
+
/// - error-atproto-identity-resolve-4: DNS resolution failed (no records)
160
160
+
/// - error-atproto-identity-resolve-5: HTTP resolution failed (hostname not found)
161
161
+
fn is_soft_failure(error_str: &str) -> bool {
162
162
+
// Check for specific atproto_identity error codes that indicate "not found"
163
163
+
// rather than actual failures
164
164
+
if error_str.starts_with("error-atproto-identity-resolve-4") {
165
165
+
// DNS resolution - check if it's a "no records" scenario
166
166
+
error_str.contains("NoRecordsFound")
167
167
+
} else if error_str.starts_with("error-atproto-identity-resolve-5") {
168
168
+
// HTTP resolution - check if it's a hostname lookup failure
169
169
+
error_str.contains("No address associated with hostname") ||
170
170
+
error_str.contains("failed to lookup address information")
171
171
+
} else {
172
172
+
false
173
173
+
}
147
174
}
148
175
149
176
/// Process a single handle resolution work item
···
201
228
);
202
229
}
203
230
Ok(Err(e)) => {
204
204
-
self.metrics
205
205
-
.total_failed
206
206
-
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
231
231
+
let error_str = e.to_string();
232
232
+
233
233
+
if Self::is_soft_failure(&error_str) {
234
234
+
// This is a soft failure - handle simply doesn't support this resolution method
235
235
+
self.metrics
236
236
+
.total_not_found
237
237
+
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
238
238
+
239
239
+
// Publish not-found metrics
240
240
+
self.metrics_publisher
241
241
+
.incr("task.handle_resolution.not_found")
242
242
+
.await;
207
243
208
208
-
// Publish failure metrics
209
209
-
self.metrics_publisher
210
210
-
.incr("task.handle_resolution.failed")
211
211
-
.await;
244
244
+
debug!(
245
245
+
handle = %work.handle,
246
246
+
error = %error_str,
247
247
+
duration_ms = duration_ms,
248
248
+
"Handle not found (soft failure)"
249
249
+
);
250
250
+
} else {
251
251
+
// This is a real error
252
252
+
self.metrics
253
253
+
.total_failed
254
254
+
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
255
255
+
256
256
+
// Publish failure metrics
257
257
+
self.metrics_publisher
258
258
+
.incr("task.handle_resolution.failed")
259
259
+
.await;
212
260
213
213
-
error!(
214
214
-
handle = %work.handle,
215
215
-
error = %e,
216
216
-
duration_ms = duration_ms,
217
217
-
"Handle resolution failed"
218
218
-
);
261
261
+
error!(
262
262
+
handle = %work.handle,
263
263
+
error = %error_str,
264
264
+
duration_ms = duration_ms,
265
265
+
"Handle resolution failed"
266
266
+
);
267
267
+
}
219
268
}
220
269
Err(_) => {
221
270
self.metrics
···
406
455
assert_eq!(metrics.total_succeeded.load(Ordering::Relaxed), 0);
407
456
assert_eq!(metrics.total_failed.load(Ordering::Relaxed), 0);
408
457
assert_eq!(metrics.total_cached.load(Ordering::Relaxed), 0);
458
458
+
assert_eq!(metrics.total_not_found.load(Ordering::Relaxed), 0);
409
459
410
460
// Test incrementing
411
461
metrics.total_processed.fetch_add(1, Ordering::Relaxed);
···
415
465
assert_eq!(metrics.total_processed.load(Ordering::Relaxed), 1);
416
466
assert_eq!(metrics.total_succeeded.load(Ordering::Relaxed), 1);
417
467
assert_eq!(metrics.total_cached.load(Ordering::Relaxed), 1);
468
468
+
}
469
469
+
470
470
+
#[test]
471
471
+
fn test_is_soft_failure() {
472
472
+
// Test DNS NoRecordsFound pattern (error-atproto-identity-resolve-4)
473
473
+
let dns_no_records = "error-atproto-identity-resolve-4 DNS resolution failed: ResolveError { kind: Proto(ProtoError { kind: NoRecordsFound { query: Query { name: Name(\"_atproto.noahshachtman.bsky.social.railway.internal.\"), query_type: TXT, query_class: IN }, soa: None, ns: None, negative_ttl: None, response_code: NotImp, trusted: true, authorities: None } }) }";
474
474
+
assert!(HandleResolverTask::is_soft_failure(dns_no_records));
475
475
+
476
476
+
// Test HTTP hostname not found pattern (error-atproto-identity-resolve-5)
477
477
+
let http_no_hostname = "error-atproto-identity-resolve-5 HTTP resolution failed: reqwest::Error { kind: Request, url: \"https://mattie.thegem.city/.well-known/atproto-did\", source: hyper_util::client::legacy::Error(Connect, ConnectError(\"dns error\", Custom { kind: Uncategorized, error: \"failed to lookup address information: No address associated with hostname\" })) }";
478
478
+
assert!(HandleResolverTask::is_soft_failure(http_no_hostname));
479
479
+
480
480
+
// Test alternate HTTP hostname failure message
481
481
+
let http_lookup_failed = "error-atproto-identity-resolve-5 HTTP resolution failed: reqwest::Error { kind: Request, url: \"https://example.com/.well-known/atproto-did\", source: hyper_util::client::legacy::Error(Connect, ConnectError(\"dns error\", Custom { kind: Uncategorized, error: \"failed to lookup address information\" })) }";
482
482
+
assert!(HandleResolverTask::is_soft_failure(http_lookup_failed));
483
483
+
484
484
+
// Test DNS error that is NOT a soft failure (different DNS error)
485
485
+
let dns_real_error = "error-atproto-identity-resolve-4 DNS resolution failed: timeout";
486
486
+
assert!(!HandleResolverTask::is_soft_failure(dns_real_error));
487
487
+
488
488
+
// Test HTTP error that is NOT a soft failure (connection timeout)
489
489
+
let http_timeout = "error-atproto-identity-resolve-5 HTTP resolution failed: connection timeout";
490
490
+
assert!(!HandleResolverTask::is_soft_failure(http_timeout));
491
491
+
492
492
+
// Test HTTP error that is NOT a soft failure (500 error)
493
493
+
let http_500 = "error-atproto-identity-resolve-5 HTTP resolution failed: status code 500";
494
494
+
assert!(!HandleResolverTask::is_soft_failure(http_500));
495
495
+
496
496
+
// Test QuickDID errors should never be soft failures
497
497
+
let quickdid_error = "error-quickdid-resolve-1 Failed to resolve subject: internal server error";
498
498
+
assert!(!HandleResolverTask::is_soft_failure(quickdid_error));
499
499
+
500
500
+
// Test other atproto_identity error codes should not be soft failures
501
501
+
let other_atproto_error = "error-atproto-identity-resolve-1 Some other error";
502
502
+
assert!(!HandleResolverTask::is_soft_failure(other_atproto_error));
418
503
}
419
504
}