|
6 | 6 | from concurrent.futures import CancelledError as FutureCancelledError |
7 | 7 | from concurrent.futures import TimeoutError as FutureTimeoutError |
8 | 8 | from concurrent.futures.thread import ThreadPoolExecutor |
| 9 | +from contextlib import contextmanager |
9 | 10 | from enum import Enum |
10 | 11 | from functools import cache |
11 | 12 |
|
|
24 | 25 |
|
25 | 26 | logger = logging.getLogger(__name__) |
26 | 27 |
|
| 28 | +# Loggers to suppress during platform detection to avoid noise in customer logs |
| 29 | +_LOGGERS_TO_SUPPRESS = [ |
| 30 | + "snowflake.connector.vendored.urllib3.connectionpool", |
| 31 | + "botocore.utils", |
| 32 | + "botocore.httpsession", |
| 33 | + "urllib3.connectionpool", |
| 34 | +] |
| 35 | + |
| 36 | + |
| 37 | +@contextmanager |
| 38 | +def _suppress_platform_detection_logs(): |
| 39 | + """ |
| 40 | + Context manager to completely suppress all logs from underlying HTTP libraries during platform detection. |
| 41 | +
|
| 42 | + This prevents any logs (including errors/warnings) from urllib3 and botocore when detecting |
| 43 | + cloud platforms, which can confuse customers (SNOW-2204396). Our own debug logs are not affected. |
| 44 | + """ |
| 45 | + original_levels = {} |
| 46 | + try: |
| 47 | + # Completely suppress all logs from noisy libraries |
| 48 | + for logger_name in _LOGGERS_TO_SUPPRESS: |
| 49 | + lib_logger = logging.getLogger(logger_name) |
| 50 | + original_levels[logger_name] = lib_logger.level |
| 51 | + lib_logger.setLevel(logging.CRITICAL + 1) # Above CRITICAL = no logs at all |
| 52 | + yield |
| 53 | + finally: |
| 54 | + # Restore original log levels |
| 55 | + for logger_name, level in original_levels.items(): |
| 56 | + logging.getLogger(logger_name).setLevel(level) |
| 57 | + |
27 | 58 |
|
28 | 59 | class _DetectionState(Enum): |
29 | 60 | """Internal enum to represent the detection state of a platform.""" |
@@ -443,74 +474,82 @@ def detect_platforms( |
443 | 474 | use_pooling=False, max_retries=0 |
444 | 475 | ) |
445 | 476 |
|
446 | | - # Run environment-only checks synchronously (no network calls, no threading overhead) |
447 | | - platforms = { |
448 | | - "is_aws_lambda": is_aws_lambda(), |
449 | | - "is_azure_function": is_azure_function(), |
450 | | - "is_gce_cloud_run_service": is_gcp_cloud_run_service(), |
451 | | - "is_gce_cloud_run_job": is_gcp_cloud_run_job(), |
452 | | - "is_github_action": is_github_action(), |
453 | | - } |
454 | | - |
455 | | - # Run network-calling functions in parallel |
456 | | - if platform_detection_timeout_seconds != 0.0: |
457 | | - with ThreadPoolExecutor(max_workers=6) as executor: |
458 | | - futures = { |
459 | | - "is_ec2_instance": executor.submit( |
460 | | - is_ec2_instance, platform_detection_timeout_seconds |
461 | | - ), |
462 | | - "has_aws_identity": executor.submit( |
463 | | - has_aws_identity, platform_detection_timeout_seconds |
464 | | - ), |
465 | | - "is_azure_vm": executor.submit( |
466 | | - is_azure_vm, |
467 | | - platform_detection_timeout_seconds, |
468 | | - session_manager, |
469 | | - ), |
470 | | - "has_azure_managed_identity": executor.submit( |
471 | | - has_azure_managed_identity, |
472 | | - platform_detection_timeout_seconds, |
473 | | - session_manager, |
474 | | - ), |
475 | | - "is_gce_vm": executor.submit( |
476 | | - is_gce_vm, |
477 | | - platform_detection_timeout_seconds, |
478 | | - session_manager, |
479 | | - ), |
480 | | - "has_gcp_identity": executor.submit( |
481 | | - has_gcp_identity, |
482 | | - platform_detection_timeout_seconds, |
483 | | - session_manager, |
484 | | - ), |
485 | | - } |
486 | | - |
487 | | - # Enforce timeout at executor level - all parallel detections must complete |
488 | | - # within platform_detection_timeout_seconds |
489 | | - for key, future in futures.items(): |
490 | | - try: |
491 | | - platforms[key] = future.result( |
492 | | - timeout=platform_detection_timeout_seconds |
493 | | - ) |
494 | | - except (FutureTimeoutError, FutureCancelledError): |
495 | | - # Thread/future timed out at executor level |
496 | | - platforms[key] = _DetectionState.WORKER_TIMEOUT |
497 | | - except Exception: |
498 | | - # Any other error from the thread |
499 | | - platforms[key] = _DetectionState.NOT_DETECTED |
500 | | - |
501 | | - detected_platforms = [] |
502 | | - for platform_name, detection_state in platforms.items(): |
503 | | - if detection_state == _DetectionState.DETECTED: |
504 | | - detected_platforms.append(platform_name) |
505 | | - elif detection_state in ( |
506 | | - _DetectionState.HTTP_TIMEOUT, |
507 | | - _DetectionState.WORKER_TIMEOUT, |
508 | | - ): |
509 | | - detected_platforms.append(f"{platform_name}_timeout") |
510 | | - |
511 | | - logger.debug( |
512 | | - "Platform detection completed. Detected platforms: %s", detected_platforms |
513 | | - ) |
514 | | - return detected_platforms |
| 477 | + # HTTP timeout should be slightly shorter than thread timeout to allow HTTP-level |
| 478 | + # timeouts to occur before thread executor times out. This helps distinguish between |
| 479 | + # HTTP_TIMEOUT (network issue) and WORKER_TIMEOUT (thread stuck/hung). |
| 480 | + http_timeout_epsilon = 0.05 # 5% shorter |
| 481 | + http_timeout = platform_detection_timeout_seconds * (1 - http_timeout_epsilon) |
| 482 | + threads_timeout = platform_detection_timeout_seconds |
| 483 | + |
| 484 | + # Suppress noisy logs from underlying HTTP libraries during platform detection |
| 485 | + with _suppress_platform_detection_logs(): |
| 486 | + # Run environment-only checks synchronously (no network calls, no threading overhead) |
| 487 | + platforms = { |
| 488 | + "is_aws_lambda": is_aws_lambda(), |
| 489 | + "is_azure_function": is_azure_function(), |
| 490 | + "is_gce_cloud_run_service": is_gcp_cloud_run_service(), |
| 491 | + "is_gce_cloud_run_job": is_gcp_cloud_run_job(), |
| 492 | + "is_github_action": is_github_action(), |
| 493 | + } |
| 494 | + |
| 495 | + # Run network-calling functions in parallel |
| 496 | + if platform_detection_timeout_seconds != 0.0: |
| 497 | + with ThreadPoolExecutor(max_workers=6) as executor: |
| 498 | + futures = { |
| 499 | + "is_ec2_instance": executor.submit( |
| 500 | + is_ec2_instance, http_timeout |
| 501 | + ), |
| 502 | + "has_aws_identity": executor.submit( |
| 503 | + has_aws_identity, http_timeout |
| 504 | + ), |
| 505 | + "is_azure_vm": executor.submit( |
| 506 | + is_azure_vm, |
| 507 | + http_timeout, |
| 508 | + session_manager, |
| 509 | + ), |
| 510 | + "has_azure_managed_identity": executor.submit( |
| 511 | + has_azure_managed_identity, |
| 512 | + http_timeout, |
| 513 | + session_manager, |
| 514 | + ), |
| 515 | + "is_gce_vm": executor.submit( |
| 516 | + is_gce_vm, |
| 517 | + http_timeout, |
| 518 | + session_manager, |
| 519 | + ), |
| 520 | + "has_gcp_identity": executor.submit( |
| 521 | + has_gcp_identity, |
| 522 | + http_timeout, |
| 523 | + session_manager, |
| 524 | + ), |
| 525 | + } |
| 526 | + |
| 527 | + # Enforce timeout at executor level - all parallel detections must complete |
| 528 | + # within threads_timeout |
| 529 | + for key, future in futures.items(): |
| 530 | + try: |
| 531 | + platforms[key] = future.result(timeout=threads_timeout) |
| 532 | + except (FutureTimeoutError, FutureCancelledError): |
| 533 | + # Thread/future timed out at executor level |
| 534 | + platforms[key] = _DetectionState.WORKER_TIMEOUT |
| 535 | + except Exception: |
| 536 | + # Any other error from the thread |
| 537 | + platforms[key] = _DetectionState.NOT_DETECTED |
| 538 | + |
| 539 | + detected_platforms = [] |
| 540 | + for platform_name, detection_state in platforms.items(): |
| 541 | + if detection_state == _DetectionState.DETECTED: |
| 542 | + detected_platforms.append(platform_name) |
| 543 | + elif detection_state in ( |
| 544 | + _DetectionState.HTTP_TIMEOUT, |
| 545 | + _DetectionState.WORKER_TIMEOUT, |
| 546 | + ): |
| 547 | + detected_platforms.append(f"{platform_name}_timeout") |
| 548 | + |
| 549 | + logger.debug( |
| 550 | + "Platform detection completed. Detected platforms: %s", |
| 551 | + detected_platforms, |
| 552 | + ) |
| 553 | + return detected_platforms |
515 | 554 | except Exception: |
516 | 555 | return [] |
0 commit comments