Ray를 Microsoft Windows로 이식

작성자 : Johny vino

(Mehrdad Niknami) (2020 년 9 월 28 일)

배경

Ray 가 처음 출시되었을 때 UNIX 기반 운영 체제 인 Linux 및 macOS 용으로 작성되었습니다. 가장 널리 사용되는 데스크톱 OS 인 Windows에 대한 지원은 부족했지만 프로젝트의 장기적인 성공을 위해 중요했습니다. Linux 용 Windows 하위 시스템 (WSL)이 일부 사용자에게 가능한 옵션을 제공했지만 최신 버전의 Windows 10에 대한 지원이 제한되어 코드 작성이 훨씬 더 어려워졌습니다. 기본 Windows 운영 체제와 상호 작용하는 것은 사용자에게 좋지 않은 경험이었습니다. 이러한 점을 감안할 때 우리는 실제로 사용자에게 기본 Windows 지원을 제공하기를 바랐습니다.

그러나 Ray를 Windows로 이식하는 것은 사소한 작업과는 거리가 멀었습니다. 이후 이식성을 확보하려는 다른 많은 프로젝트와 마찬가지로 솔루션이 명확하지 않은 많은 문제에 직면했습니다. 이 블로그 게시물에서 우리는 Ray를 Windows와 호환되도록 만드는 프로세스의 기술적 세부 사항에 대해 자세히 알아 보려고합니다. 비슷한 비전을 가진 다른 프로젝트가 관련된 잠재적 인 문제와 그 처리 방법을 이해하는 데 도움이되기를 바랍니다.

개요

Windows에 대한 재조정 지원은 그 어느 때보 다 큰 도움이되었습니다. Ray에 대한 개발이 더 진행됨에 따라 도전. 시작하기 전에 다음을 포함하여 코드의 특정 측면이 이식성 노력의 상당 부분을 구성 할 것으로 예상했습니다.

  • 프로세스 간 통신 (IPC) 및 공유 메모리
  • 객체 핸들 및 파일 핸들 / 설명자 (FD)
  • 시그널링을 포함한 프로세스 생성 및 관리
  • 파일 관리, 스레드 관리 및 비동기 I / O
  • 쉘 및 시스템 명령 사용
  • Redis

문제의 정도를 고려할 때, 우리는 기존의 문제를 해결하는 것보다 더 많은 비 호환성이 도입되는 것을 방지하는 것이 우선되어야한다는 것을 빠르게 깨달았습니다. 문제. 따라서 특정 문제가 때때로 다른 단계에서 해결되기 때문에 다소 단순화되었지만 대략 다음 단계로 진행하려고했습니다.

  1. 타사 종속성에 대한 호환성
  2. Ray의 컴파일 가능성 (빈 스텁 & TODO를 통해)
  3. 연결 가능성
  4. 지속적 통합 (CI) (호환되지 않는 추가 변경을 차단하기 위해)
  5. 정적 호환성 (대부분 C ++)
  6. 런타임 실행 가능성 (최소 POC)
  7. 런타임 호환성 (대부분 Python)
  8. 실행 시간 개선 (예 : 유니 코드 지원)
Ashkan Forouzani

개발 프로세스

높은 수준에서 여기의 접근 방식은 다르다. 소규모 프로젝트의 경우 코드를 새 플랫폼으로 이식하는 동시에 코드베이스를 단순화하는 것이 매우 유용 할 수 있습니다. 그러나 우리가 취한 접근 방식은 동적으로 변화하는 대규모 코드베이스에 상당히 도움이되었으며 문제를 한 번에 하나씩 처리하는 것이 었습니다 , 변경 사항을 최대한 서로 직각으로 유지 단순성보다 시맨틱 보존을 우선시합니다 .

때로는 프로덕션에서 반드시 발생하지 않았을 수도있는 조건을 처리하기 위해 잠재적으로 관련없는 코드를 작성해야했습니다 (예 : 공백이없는 파일 경로). 다른 경우에는 피할 수 있었던보다 일반적인 목적의 “기계적”솔루션을 도입해야하는 경우도 있습니다 (예 : 다른 디자인이있을 수있는 특정 경우에 std::shared_ptr 사용). std::unique_ptr 사용이 허용되었습니다). 그러나 기존 코드의 의미를 보존하는 것은 나머지 팀에게 영향을 미칠 새로운 버그가 코드베이스에 지속적으로 도입되는 것을 방지하는 데 절대적으로 중요했습니다. 그리고 돌이켜 보면이 접근 방식은 매우 성공적이었습니다. Windows 관련 변경으로 인한 다른 플랫폼의 버그는 매우 드물었 고 코드베이스의 의미 변경으로 인한 것보다 종속성 변경으로 인해 가장 자주 발생했습니다.

컴파일 가능성 (타사 종속성)

첫 번째 장애물은 타사 종속성이 Windows에서 구축 될 수 있도록하는 것이 었습니다. 많은 종속성이 널리 사용되는 라이브러리 였고 대부분 주요 비 호환성이 없었지만 이것이 항상 간단하지는 않았습니다. 특히, 우발적 인 복잡성은 문제의 여러 측면에서 풍부했습니다.

  • 일부 프로젝트의 빌드 파일 (특히 Bazel 빌드 파일)은 때때로 Windows에서 부적절하여 패치 가 필요했습니다. 종종 이러한 문제는

인용 문제와 같이 UNIX 플랫폼에서 더 드물게 발생하는 문제로 인해 발생했습니다.

올바른 Python 인터프리터를 실행 하거나 공유 라이브러리와 정적 라이브러리를 올바르게 연결 하는 문제입니다. 고맙게도이 문제를 해결하는 데 가장 도움이되는 도구 중 하나는 Bazel 자체였습니다. 외부 작업 공간을 패치하는 기본 제공 기능은 우리가 빠르게 배웠 듯이 매우 유용합니다. 즉석 방식으로 빌드 프로세스를 수정하지 않고 외부 라이브러리를 패치하는 표준 방법을 허용하여 코드베이스를 깔끔하게 유지했습니다.

  • 빌드 툴체인은 컴파일러 또는 링커 선택이 종종 영향을 받았기 때문에 자체 복잡성을 나타 냈습니다. 신뢰할 수있는 라이브러리, API 및 정의. 안타깝게도 Bazel에서 컴파일러를 전환하는 것은 매우 성가신 수 있습니다. 특히 Windows에서는 Microsoft Visual C ++ 빌드 도구를 사용할 때 최상의 경험이 제공되는 경우가 많지만 다른 플랫폼의 도구 체인은 GCC 또는 Clang을 기반으로합니다. 다행히 LLVM 도구 모음은 Windows에서 Clang-Cl 과 함께 제공되므로 Clang과 MSVC 기능을 혼합하여 사용할 수 있으므로 많은 문제를 훨씬 쉽게 해결할 수 있습니다.
  • 라이브러리 의존성은 일반적으로 가장 어려움을 나타냅니다. 때로는 문제가 누락 된 헤더 나 기계적으로 처리 할 수있는 충돌하는 정의만큼 평범했으며 널리 사용되는 라이브러리 (예 : Boost)조차도 영향을받지 않았습니다. 이에 대한 해결책은 몇 가지 적절한 매크로 정의 또는 더미 헤더였습니다. hiredis Arrow 라이브러리와 같은 다른 경우에는 라이브러리에 다음과 같은 POSIX 기능이 필요합니다. Windows에서는 사용할 수 없습니다 (예 : 신호 또는 UNIX 도메인 소켓을 통해 파일 설명자를 전달하는 기능). 어떤 경우에는 해결하기가 훨씬 더 어려웠습니다. 그러나이 단계에서 컴파일 가능성에 대해 더 염려했기 때문에 더 복잡한 API의 구현을 이후 단계로 연기하고 기본 스텁을 활용하거나 문제가되는 코드를 비활성화하여 빌드를 계속하는 것이 유익했습니다.
  • 종속성 컴파일을 마친 후에는 Ray 자체의 핵심을 컴파일하는 데 집중할 수있었습니다.

    Compilability (Ray)

    다음 장애물은 Ray 자체를 Plasma 저장소 ( Arrow 의 일부)와 함께 컴파일합니다. 코드가 종종 Windows 모델 용으로 설계되지 않았고 POSIX API에 크게 의존했기 때문에 이것은 더 어려웠습니다. 어떤 경우에는 이는 iv id에 대해 sys/time.h 대신 적절한 헤더 (예 : WinSock2.h를 찾아 사용하는 문제)였습니다. = “81f9895d86″>

    (놀라 울 수 있음) 또는 대체 항목 생성 ( 예 :

    unistd.h ). 다른 경우에는 호환되지 않는 코드를 비활성화하고 향후 해결을 위해 TODO를 남겨 두었습니다 .

    이는 개념적으로 다음의 경우와 유사했습니다. 타사 종속성을 처리 할 때 여기에 나타난 고유 한 높은 수준의 우려 사항은 코드베이스의 변경을 최소화하는 것입니다. 가능한 가장 우아한 솔루션. 특히, 팀이 POSIX API를 가정하고 코드베이스를 지속적으로 업데이트하고 코드베이스가 아직 Windows와 컴파일되지 않았기 때문에 “드롭 인”이었고 최소한의 변경으로 투명하게 채택되어 전체적인 호환성을 얻을 수있었습니다. 전체 코드베이스는 본질적으로 외과 적 솔루션보다 구문 론적 또는 의미 론적 병합 충돌을 피하는 데 훨씬 더 유용했습니다. 이 사실로 인해 모든 호출 사이트를 수정하는 대신 원하는 동작을 시뮬레이션하는 POSIX API 용 Windows 을 만들었습니다. 사무용 겉옷. 이를 통해 컴파일 가능성을 보장하고 호환되지 않는 변경 사항의 확산을 방지 할 수있었습니다 (그리고 나중에 일괄 적으로 전체 코드베이스에서 각 개별 문제를 일관되게 해결).

    연결 가능성

    컴파일 가능성이 확보 된 후 다음 문제는 실행 가능한 바이너리를 얻기위한 개체 파일의 적절한 연결이었습니다.이론적으로는 간단하지만 연결성 문제는 종종 다음과 같은 많은 우발적 인 복잡성을 수반합니다.

    • 특정 시스템 라이브러리는 플랫폼에 따라 다르며 사용할 수 없거나 다른 플랫폼에서 다릅니다 ( 예 : libpthread)
    • 특정 라이브러리는 링크 될 것으로 예상되었을 때 동적으로 링크되었습니다. 정적으로 (또는 그 반대)
    • 특정 기호 정의가 누락되었거나 충돌했습니다 (예 : hiredis iv id에서 connect 소켓의 경우 connect와 = “f72a34a448″>

    충돌 )

  • 특정 종속성은 POSIX 시스템에서 우연히 작동했지만 명시적인 처리가 필요했습니다. Windows의 Bazel
  • 이에 대한 솔루션은 빌드 파일에 대한 평범한 (아마도 분명하지 않지만) 변경 사항 이었지만 경우에 따라 종속성을 패치해야했습니다. 이전과 마찬가지로 외부 소스의 거의 모든 파일에 패치를 적용하는 Bazel의 기능은 이러한 문제를 해결하는 데 매우 도움이되었습니다.

    By Richy Great

    성공적으로 빌드하기위한 코드베이스는 중요한 이정표입니다. 플랫폼 별 변경 사항이 통합되면 이제 전체 팀이 향후 모든 변경 사항에서 정적 이식성을 보장 할 책임이 있으며 호환되지 않는 변경 사항의 도입은 코드베이스 전체에서 최소화됩니다. (물론 동적 이식성은이 시점에서 여전히 가능하지 않았습니다. 스텁은 종종 Windows에서 필요한 기능이 부족하여이 단계에서 코드를 실행할 수 없었기 때문입니다.)

    코드베이스를 성공적으로 구축하는 것은 중요한 이정표였습니다. 플랫폼 별 변경 사항이 통합되면 이제 전체 팀이 향후 모든 변경 사항에서 정적 이식성을 보장 할 책임이 있으며 호환되지 않는 변경 사항의 도입은 코드베이스 전체에서 최소화됩니다. (물론 동적 이식성은이 시점에서 여전히 가능하지 않았습니다. 스텁은 종종 Windows에서 필요한 기능이 부족하여이 단계에서 코드를 실행할 수 없었기 때문입니다.)

    이로 인해 워크로드가 감소했을뿐만 아니라 코드베이스에 지속적으로 도입되는 주요 변경 사항에주의를 돌릴 필요없이 Windows 이식성 노력을 늦추고 더 느리고 신중하게 진행하여 더 어려운 비 호환성을 심층적으로 해결할 수있었습니다. 이를 통해 다른 플랫폼의 의미 체계에 잠재적으로 영향을 미칠 수있는 코드베이스의 일부 리팩토링을 포함하여 장기적으로 고품질 솔루션이 가능했습니다.

    정적 호환성 (대부분 C / C ++)

    호환성 스텁을 제거하거나 정교하게 만드는 가장 시간이 많이 걸리는 단계 일 수 있으며 비활성화 된 코드를 다시 활성화하고 개별 문제를 해결할 수 있습니다. C ++의 정적 특성으로 인해 컴파일러 진단은 코드를 전혀 실행하지 않고도 필요한 대부분의 변경 사항을 안내 할 수 있습니다.

    앞에서 언급 한 일부 변경 사항을 포함하여 여기에서 개별 변경 사항은 깊이 논의하기에는 너무 길 것입니다. 전체적으로, 특정 개별 문제는 자신의 긴 블로그 게시물에 가치가있을 것입니다. 예를 들어 프로세스 생성 및 관리는 실제로 좋지 않은 것처럼 보이는 특이성과 함정으로 가득 찬 놀랍도록 복잡한 작업입니다 (예 : 여기 참조). 이것은 오래된 문제 임에도 불구하고 크로스 플랫폼 라이브러리입니다. 이 중 일부는 운영 체제 API 자체의 초기 설계가 잘못 되었기 때문입니다. ( Chromium 소스 코드 는 다른 곳에서 종종 무시되는 몇 가지 복잡성을 보여줍니다. 실제로 Chromium 소스 코드는 종종 많은 플랫폼 비 호환성 및 미묘함.)이 사실에 대한 우리의 초기 무시로 인해 Boost.Process를 사용하려고 시도했지만 POSIX의 pid_t (둘 다에 사용되는 명확한 소유권 의미 체계가 없음) 프로세스 식별 및 소유권)뿐만 아니라 Boost.Process 라이브러리 자체의 버그로 인해 코드베이스에서 버그를 찾기가 어려웠으며 궁극적으로이 변경 사항을 되돌리고 자체 추상화를 도입하기로 결정했습니다. 또한 Boost.Process 라이브러리는 Boost 라이브러리에서도 상당히 무거웠 기 때문에 빌드 속도가 상당히 느려졌습니다. 대신 프로세스 객체에 대해 우리 자신의 래퍼 를 작성했습니다. 이것은 우리의 목적에 아주 잘 맞는 것으로 판명되었습니다. 이 경우에 우리의 시사점 중 하나는 우리 자신의 필요에 맞게 솔루션을 조정하는 것을 고려하고 기존 솔루션이 최선의 선택이라고 가정하지 않는 것입니다 .

    물론 이것은 프로세스 관리 문제를 간략히 보여준 것입니다.이식성 노력의 다른 측면도 자세히 살펴볼 가치가 있습니다.

    By Thomas Jensen

    코드베이스의 일부 (예 : Arrow의 Plasma 서버와의 통신)가 가정 됨 Windows에서 UNIX 도메인 소켓 (AF_UNIX)을 사용하는 기능. 최신 버전의 Windows 10은 UNIX 도메인 소켓을 지원 하지만 구현은 UNIX 도메인의 가능한 모든 사용을 다루기에 충분하지 않습니다. 소켓이나 UNIX 도메인 소켓은 특히 우아하지 않습니다. 수동 정리가 필요하고 프로세스가 깨끗하지 않게 종료되는 경우 파일 시스템에 불필요한 파일을 남길 수 있으며 보조 데이터를 보낼 수있는 기능을 제공하지 않습니다 (예 : 파일 설명자)를 다른 프로세스에 추가합니다. Ray가 Boost.Asio를 사용하기 때문에 UNIX 도메인 소켓을 대체하는 가장 편리한 방법은 로컬 TCP 소켓 (둘 다 일반 소켓으로 추상화 할 수 있음) 이었으므로 후자 인 교체 합니다.

    TCP 소켓을 사용해도 여전히 복제 기능을 제공하지 않았기 때문에 이것은 충분하지 않았습니다. 다른 프로세스에 소켓 설명자. 사실,이 문제에 대한 적절한 해결책은 그것을 완전히 피하는 것이었을 것입니다. 그러나 코드베이스의 관련 부분 (다른 사람들이 착수 한 작업)에 대한 장기적인 리팩토링이 필요하기 때문에 투명한 접근 방식이 그 사이에 더 적절 해 보였습니다. 이것은 UNIX 기반 시스템에서 파일 설명자를 복제 할 때 대상 프로세스의 ID에 대한 지식이 아니라 있다는 사실로 인해 어려워졌지만 Windows에서는 핸들을 복제하려면 적극적으로 대상 프로세스. 이러한 문제를 해결하기 위해 Plasma 저장소를 시작할 때 핸드 셰이크 절차를 TCP 연결을 설정하는보다 전문적인 메커니즘 으로 대체하여 파일 설명자를 교환하는 기능을 구현했습니다. , 다른 쪽 끝에서 프로세스의 ID를 찾고 (아마도 느리지 만 일회성 절차) 소켓 핸들을 복제하고 대상 프로세스에 새 핸들을 알립니다. 이것은 범용 솔루션이 아니며 (실제로 일반적인 경우 경쟁 조건에 취약 할 수 있음) 매우 비정상적인 접근 방식이지만 Ray의 목적에 적합하며 동일한 문제에 직면 한 다른 프로젝트의 접근 방식 일 수 있습니다.

    이 외에도 직면 할 것으로 예상되는 가장 큰 문제 중 하나는 Redis 서버 종속성이었습니다. Microsoft Open Technologies (MSOpenTech)는 이전에 Windows에 Redis 포트 를 구현했지만 프로젝트가 중단되어 Ray에 필요한 Redis 버전을 지원하지 않았습니다. . 이로 인해 처음에는 Linux 용 Windows 하위 시스템 (WSL)에서 Redis 서버를 계속 실행해야한다고 가정했으며, 이는 사용자에게 불편 함이 입증되었을 것입니다. 그런 다음 다른 개발자가 Windows에서 나중에 Redis 바이너리를 생성하기 위해 프로젝트를 계속했다는 사실을 알게되어 매우 감사했습니다 ( tporadowski / redis 참조). 이것은 우리의 문제를 엄청나게 단순화 시켰고 우리가 Windows 용 Ray의 기본 지원을 제공 할 수있게 해주었습니다.

    마지막으로, 우리가 직면 한 가장 중요한 장애물 (MSOpenTech Redis와 대부분의 다른 POSIX 전용 프로그램과 마찬가지로)은 다음과 같습니다. Windows에서 일부 POSIX API에 대한 사소한 대체가 없습니다. 이들 중 일부 ( 예 :

    getppid()) 다소 지루하지만 간단했습니다. 그러나 전체 포팅 과정에서 발생하는 가장 어려운 문제는 파일 설명자 대 파일 핸들 문제였습니다. 우리가 의존했던 대부분의 코드 (예 : Arrow의 Plasma 저장소)는 POSIX 파일 설명자 (int s)를 사용한다고 가정했습니다. 그러나 Windows는 기본적으로 포인터 크기의 size_t와 유사한 HANDLE를 사용합니다. 그러나 Microsoft Visual C ++ 런타임 (CRT)이 POSIX와 유사한 계층을 제공하므로 그 자체로는 중요한 문제가 아닙니다. 그러나 계층은 기능이 제한되어 있으며이를 지원하지 않는 모든 호출 사이트에서 번역이 필요하며, 특히 소켓이나 공유 메모리 핸들과 같은 것에 사용할 수 없습니다. 더욱이 HANDLE가 항상 32 비트 정수에 맞도록 충분히 작을 것이라고 가정하고 싶지 않았습니다. 우리가 알지 못했던 상황은이 가정을 조용히 깨뜨릴 수 있습니다.가장 확실한 해결책은 Arrow와 같은 라이브러리에서 파일 설명자를 나타내는 모든 int를 감지하고이를 대체하는 것이었기 때문에 이로 인해 문제가 상당히 복잡해졌습니다. ) 오류가 발생하기 쉬운 프로세스였으며 외부 코드에 상당한 패치를 적용하여 상당한 유지 관리 부담이 발생했습니다.

    이 단계에서 수행 할 작업을 결정하는 것은 상당히 어려웠습니다. 동일한 문제에 대한 MSOpenTech Redis의 솔루션 기존 CRT 구현의 상단 에 단일 프로세스 전체 파일 설명자 테이블을 생성 하여 스레드 안전성을 처리하고 모든 POSIX API (이미 처리 할 수있는 API 포함)를 단순히 파일 설명자를 번역하는 데 사용합니다. 대신, 우리는 특이한 접근 방식을 취하기로 결정했습니다. 우리는 CRT의 POSIX 번역 레이어를 확장 했습니다 . 이는 생성시 호환되지 않는 핸들을 식별하고 해당 핸들을 불필요한 파이프의 버퍼에 “밀어 넣고”대신 해당 파이프의 설명자를 반환함으로써 수행되었습니다. 그런 다음 이러한 핸들의 사용 사이트를 수정하기 만하면되었습니다.이 핸들은 모두 socket 및 메모리 매핑 파일 API. 사실, 이것은 매크로 를 통해 많은 기능을 리디렉션 할 수 있었기 때문에 패치의 필요성을 피하는 데 도움이되었습니다.

    이 고유 한 확장 레이어 (win32fd.h에서)를 개발하는 것은 중요했고 (매우 비 정통적) 번역 레이어처럼 그만한 가치가있었습니다. 는 실제로 비교하면 매우 작았으며 대부분의 관련없는 문제 (예 : 파일 설명자 테이블의 다중 스레드 잠금)를 CRT API에 위임 할 수있었습니다. 또한 동일한 전역 파일 설명자 테이블에서 익명 파이프를 활용 하여 (직접 액세스 할 수 없음에도 불구하고) 인터셉트하고 번역하지 않아도됩니다. 이미 직접 처리 할 수있는 다른 기능에 대한 파일 설명자. 이를 통해 나중에 코드를 리팩토링하고 더 높은 수준 (예 : Boost.Asio 래퍼를 통해)에서 더 나은 래퍼를 제공 할 수있을 때까지 최소한의 성능 영향으로 대부분의 코드가 본질적으로 변경되지 않은 상태로 유지되었습니다. 이 레이어를 확장하면 Redis와 같은 다른 프로젝트를 훨씬 더 원활하게 Windows로 이식 할 수 있으며 훨씬 덜 과감한 변경이나 버그 가능성이 있습니다.

    작성자 : Andrea Leopardi

    런타임 실행 가능성 (개념 증명)

    Ray 코어가 제대로 작동한다고 믿었을 때 다음 단계는 Python 테스트가 코드 경로를 성공적으로 실행할 수 있는지 확인하는 것이 었습니다. 처음에는 우선 순위를 정하지 않았습니다. 그러나 나중에 팀의 다른 개발자가 변경하여 실제로 Windows와 더 동적 인 비 호환성을 도입하고 CI 시스템이 이러한 손상을 감지 할 수 없었기 때문에 이것은 실수로 판명되었습니다. 따라서 이후 빌드가 더 이상 손상되지 않도록 Windows 빌드에서 최소 테스트를 실행 하는 것을 우선 순위로 삼았습니다.

    대부분의 노력은 성공적이었고, Ray 코어에 남아있는 버그는 코드베이스의 예측 가능한 부분에있었습니다 (하지만 문제를 해결하려면 종종 사소한 것과는 거리가 먼 다중 프로세스 코드를 단계별로 실행해야 함). 그러나 C 측과 Python 측에 적어도 하나의 다소 불쾌한 놀라움이 있었는데, 둘 다 (무엇보다도) 우리가 미래에 소프트웨어 문서를 더 적극적으로 읽도록 장려했습니다.

    C 측에서 소켓 핸들 교환을위한 초기 핸드 셰이크 래퍼 는 순진하게 sendmsgrecvmsg (WSASendMsgWSARecvMsg 포함). 이러한 Windows API는 POSIX API에 가장 근접한 것이므로 명백한 선택 인 것으로 보입니다. 그러나 실행시 코드가 지속적으로 충돌하고 문제의 원인이 명확하지 않습니다. 일부 디버깅 (빌드 & 런타임의 디버그 버전 포함)은 문제가 WSASendMsg에 전달 된 스택 변수에 있음을 드러내는 데 도움이되었습니다. 메모리 내용에 대한 추가 디버깅 및 면밀한 검사는 문제가 WSAMSGmsg_flags 필드 일 수 있음을 시사했습니다. 이것이 유일한 초기화되지 않았기 때문입니다. 들.그러나 이것은 관련이없는 것처럼 보였습니다. msg_flagsstruct msghdrflags에서 번역 된 것일뿐입니다. 이것은 입력에 사용되지 않고 단순히 출력 매개 변수로 사용되었습니다. 그러나 문서를 읽으면서 문제가 드러났습니다. Windows에서이 필드는 input 매개 변수로도 사용되었으므로 초기화되지 않은 상태로두면 예기치 않은 동작이 발생했습니다! 이것은 우리에게 매우 예상치 못한 일이었으며 앞으로 나아가는 두 가지 중요한 사항이 있습니다. 모든 함수의 문서를주의 깊게 읽고 또한 변수를 초기화하는 것은 현재 API의 정확성을 보장하는 것뿐만 아니라 대상 API의 향후 변경에 대해 강력한 코드를 만드는 데에도 중요합니다. .

    Python 측에서 다른 문제가 발생했습니다. 우리의 네이티브 Python 모듈은 명백한 문제가 없음에도 불구하고 처음에는로드에 실패했습니다. 여러 날의 추측, 어셈블리 및 CPython 소스 코드를 단계별로 살펴보고 CPython 코드베이스의 변수를 검사 한 후 문제가 동적 Python에 .pyd 접미사가 없다는 것이 분명해졌습니다. Windows의 라이브러리. 밝혀진 바와 같이, 우리에게 불분명 한 이유 때문에 Python은 기본 공유 라이브러리가 일반적으로 모든 파일로도로드 될 수 있다는 사실에도 불구하고 Windows에서 Python 모듈로 .dll 파일도로드하는 것을 거부합니다. 신장. 실제로이 사실이 Python 웹 사이트에 문서화 된 것으로 밝혀졌습니다. 그러나 안타깝게도 그러한 문서가 존재한다고해서 우리가 그것을 찾을 수 있다는 것을 의미 할 수는 없습니다.

    그러나 결국 Ray는 Windows에서 성공적으로 실행할 수 있었으며 이것이 다음 마일스톤을 마무리하고 증거를 제공했습니다. 노력에 대한 개념입니다.

    작성자 : Hitesh Choudhary

    런타임 호환성 (대부분 Python)

    이 시점에서 Ray의 핵심은 일하면서 우리는 더 높은 수준의 코드를 이식하는 데 집중할 수있었습니다. 일부 문제는 해결하기가 다소 쉬웠습니다. 예를 들어, UNIX 전용 인 일부 Python API (예 : os.uname()[1])는 종종 Windows에서 적합한 대체품 (예 : ), 코드베이스에서 모든 인스턴스를 검색하는 방법을 아는 것이 중요했습니다. 다른 문제는 추적하거나 해결하기가 더 어려웠습니다. 때로는 POSIX 관련 명령 (예 : ps)을 사용했기 때문에 대체 접근 방식 (예 : psutil for Python). 다른 경우에는 타사 라이브러리의 비 호환성 때문이었습니다. 예를 들어, Windows에서 소켓 연결이 끊어지면 빈 읽기가 발생하지 않고 오류가 발생합니다. Redis 용 Python 라이브러리는이를 처리하지 않는 것으로 보입니다. 이러한 동작의 차이는 Ray 종료시 발생하는 혼동 오류를 피하기 위해 명시적인 원숭이 패치 를 필요로했습니다.

    일부 문제는 상당히 지루하지만 예상 할 수있는 작업 (예 : /tmp 사용을 플랫폼의 임시 디렉토리로 바꾸거나 모든 절대 경로가 슬래시로 시작된다는 가정을 피함)은 다소 예상치 못한 것입니다 (예 : 잘못된 가정으로 인해 포트 예약 ) 또는 (종종 그렇듯이) 충돌하며이를 이해하는 것은 Windows의 아키텍처와 이전 버전과의 호환성에 대한 접근 방식을 이해하는 데 달려 있습니다.

    이러한 이야기 ​​중 하나는 Windows에서 디렉토리 구분 기호로 슬래시를 사용하는 것입니다. 일반적으로 이들은 잘 작동하는 것처럼 보이며 일반적으로 개발자가 사용합니다. 그러나 이는 실제로 사용자 모드 Windows 하위 시스템 라이브러리에서 슬래시를 백 슬래시로 자동 변환하기 때문이며, 특정 자동 처리는 경로에 \\?\ 접두사를 명시 적으로 접두사로 지정하여 억제 할 수 있습니다. 이는 특정 호환성 기능 (예 : 긴 경로)을 우회하는 데 유용합니다. 그러나 우리는 이러한 경로를 명시 적으로 사용하지 않았으며 사용자가 실험 릴리스에서 비정상적인 사용을 피할 수 있다고 가정했습니다. 그러나 나중에 Bazel이 특정 Python 테스트를 호출 할 때 경로가이 형식으로 처리되어 긴 경로를 사용할 수 있으며 이로 인해 암시 적으로 의존하는 자동 번역이 비활성화된다는 것이 분명해졌습니다. 이로 인해 중요한 점을 알게되었습니다. 첫째, 예상치 못한 문제가 발생할 가능성이 가장 적으므로 일반적으로 대상 시스템에 가장 적합한 방식으로 API를 사용하는 것이 좋습니다. .둘째, 가장 중요한 점은 사용자의 환경을 예측할 수 있다고 가정하는 것은 오류 일뿐입니다 . 현실은 현대 소프트웨어가 거의 항상 우리가 알지 못하는 정확한 동작을 가진 타사 코드를 실행하는 데 의존한다는 것입니다. 사용자가 문제가있는 상황을 피할 수 있다고 가정하더라도 타사 소프트웨어는 이러한 숨겨진 가정을 완전히 인식하지 못합니다. 따라서 이는 사용자뿐만 아니라 소프트웨어 개발자 자신에게도 발생하여 초기 코드를 작성할 때 해결하는 것보다 추적하기가 더 어려운 버그를 초래할 가능성이 높습니다. 따라서 강력한 시스템을 설계 할 때 프로그램 친 화성 ( “사용자 친 화성”의 반대)에 너무 많은 비중을 두는 것을 피하는 것이 중요합니다.

    (재미있는 점으로, 실제로 Windows에서는 경로 실제로는 따옴표 및 일반적으로 불법으로 간주되는 기타 특수 문자를 포함 할 수 있습니다. 이는 NTFS 대체 데이터 스트림을 사용할 때 발생합니다. 그러나 이는 드물고 복잡하여 표준 언어 라이브러리에서도 처리하지 못하는 경우가 많습니다.)

    하지만 가장 중요한 문제가 해결 된 후 Windows에서 많은 테스트를 통과하여 Ray의 첫 번째 실험적인 Windows 구현을 만들었습니다.

    작성자 : Ross Sneddon

    런타임 개선 (예 : 유니 코드 지원)

    이 시점에서 Ray의 핵심 대부분은 다른 플랫폼에서와 마찬가지로 Windows에서도 사용할 수 있습니다. 그럼에도 불구하고 일부 문제는 남아 있으며이를 해결하기위한 지속적인 노력이 필요합니다.

    유니 코드 지원이 그러한 문제 중 하나입니다. 역사적 이유 때문에 Windows 사용자 모드 하위 시스템에는 대부분의 API의 두 가지 버전이 있습니다. 하나는 단일 바이트 문자 집합을 지원하는 “ANSI”버전이고 다른 하나는 UCS-2 또는 UTF-16을 지원하는 “유니 코드”버전입니다. 문제의 API 세부 사항). 불행히도 이들 중 어느 것도 UTF-8이 아닙니다. 유니 코드에 대한 기본적인 지원조차도 전체 코드베이스에서 와이드 문자열 (wchar_t 기반)을 사용해야합니다. (nb : 사실, Microsoft는 최근에 UTF-8을 코드 페이지로 도입하려고 시도했지만, 적어도 잠재적으로 문서화되지 않고 취약한 Windows 내부에 의존하지 않고는이 문제를 원활하게 해결할 수있을만큼 충분히 지원되지 않습니다.)

    전통적으로 Windows 프로그램은 좁거나 넓게 확장되는 _T() 또는 TEXT()와 같은 매크로를 사용하여 유니 코드를 처리합니다. -문자 리터럴은 유니 코드 빌드 지정 여부에 따라 달라지며 TCHAR를 일반 문자 유형으로 사용합니다. 마찬가지로 대부분의 C API에는 strlen()TCHAR 종속 버전이 있습니다 (예 : _tcslen() >) 두 유형의 코드와의 호환성을 허용합니다. 그러나 UNIX 기반 코드베이스를이 모델로 마이그레이션하는 것은 다소 복잡한 작업입니다. 이것은 아직 Ray에서 수행되지 않았으므로이 글을 쓰는 시점에서 Ray는 Windows의 파일 경로 (예 :)에서 적절한 유니 코드를 지원하지 않으며이를위한 최선의 접근 방식은 여전히 ​​열린 질문 일 수 있습니다.

    또 다른 문제는 프로세스 간 통신 메커니즘입니다. TCP 소켓은 Windows에서 잘 작동 할 수 있지만 논리에 불필요한 복잡성 (예 : 시간 초과, 연결 유지, Nagle 알고리즘) 계층을 도입하여 비 로컬 호스트에서 우발적으로 도달 할 수 있으므로 차선책입니다. 약간의 성능 오버 헤드가 발생합니다. 앞으로 Named Pipes는 Windows에서 UNIX 도메인 소켓을 더 잘 대체 할 수 있습니다. 실제로 Linux에서도 파이프 또는 소위 추상 UNIX 도메인 소켓 이 더 나은 대안이 될 수 있습니다. 파일 시스템의 소켓 파일을 복잡하게 정리하고 정리할 필요가 없기 때문입니다.

    마지막으로 이러한 문제의 또 다른 예는 BSD 소켓 호환성 또는 부족한 것입니다. StackOverflow에 대한 탁월한 답변 은 몇 가지 문제에 대해 자세히 설명하지만, 일반적인 소켓 API는 원래 BSD 소켓 API의 파생물이지만 다른 플랫폼은 유사한 소켓을 구현합니다. 다르게 플래그를 지정합니다. 특히 기존 IP 주소 또는 TCP 포트와의 충돌은 플랫폼간에 다른 동작을 생성 할 수 있습니다. 여기서 문제를 자세히 설명하기는 어렵지만 최종 결과는 동일한 호스트에서 여러 Ray 인스턴스를 동시에 사용하기 어려울 수 있다는 것입니다. (사실 이것은 OS 커널 동작에 따라 다르기 때문에 WSL에도 영향을 미칩니다.) 이것은 해결책이 다소 관련되어 있고 현재 시스템에서 완전히 해결되지 않은 또 다른 알려진 문제입니다.

    결론

    Ray와 같은 코드베이스를 Windows로 이식하는 과정은 소프트웨어 개발의 여러 측면의 장단점과 코드 유지 관리에 미치는 영향을 강조하는 귀중한 경험이었습니다.앞의 설명은 도중에 발생하는 장애물 중 일부만 강조합니다. 프로세스에서 많은 유용한 결론을 도출 할 수 있으며, 그중 일부는 유사한 목표를 달성하고자하는 다른 프로젝트에 대해 여기에서 공유하는 것이 유용 할 수 있습니다.

    첫째, 어떤 경우에는 실제로 후속 버전에서 나중에 발견했습니다. 일부 도서관 (예 : hiredis) 중 일부는 이미 우리가 해결 한 문제를 해결했습니다. (예를 들어) 최근 Redis 버전에 포함 된 hiredis 버전이 실제로 hiredis의 오래된 복사본이어서 일부 문제가 아직 해결되지 않았다고 믿게했기 때문에 솔루션이 항상 분명하지는 않았습니다. 또한 이후 개정판은 항상 기존의 모든 호환성 문제를 완전히 해결하지 못했습니다. 그럼에도 불구하고 일부 문제에 대한 기존 솔루션을 더 자세히 확인하여 다시 해결하지 않아도되었을 수 있습니다.

    작성자 : John Barkiple

    두 번째, 소프트웨어 공급망은 종종 복잡합니다 . 버그는 자연스럽게 각 레이어에서 합성 될 수 있으며, 특히 에서 사용될 때 널리 사용되는 오픈 소스 도구가 “전투 테스트”를 거쳐 강력하다고 신뢰하는 것은 오류입니다. 분노 . 더욱이, 많은 오래되거나 일반적인 소프트웨어 엔지니어링 문제는 특히 (그러나뿐만 아니라) 다른 시스템 간의 호환성이 필요한 경우 사용할 수있는 만족스러운 솔루션이 없습니다. 실제로, Ray를 Windows로 포팅하는 과정에서 단순한 기이 한 것 외에도 Git 버그를 포함하되 이에 국한되지 않는 수많은 소프트웨어에서 정기적으로 버그를 발견하고보고했습니다. Bazel 사용에 영향을 준 Linux , Redis (Linux) , glog , psutil (WSL에 영향을 미치는 파싱 버그) , grpc , 다수 Bazel 자체에서 식별하기 어려운 버그 (예 : 1 , 2 , 3 , 4 ), Travis CI GitHub 작업 등이 있습니다. 이를 통해 우리는 종속성의 복잡성에도 더 많은주의를 기울일 수있었습니다.

    셋째, 도구 및 인프라에 투자 는 장기적으로 배당금을 지급합니다. 더 빠른 빌드는 더 빠른 개발을 허용하고 더 강력한 도구를 사용하면 복잡한 문제를 더 쉽게 해결할 수 있습니다. 우리의 경우 Bazel의 사용은 완벽과는 거리가 멀고 가파른 학습 곡선을 부과 함에도 불구하고 여러면에서 우리를 도왔습니다. 새로운 도구의 기능, 강점 및 단점을 배우기 위해 시간 (수일)을 투자하는 것은 쉽지 않지만 코드 유지 관리에 도움이 될 수 있습니다. 우리의 경우 Bazel 문서를 심층적으로 읽는 데 시간을 할애하여 미래의 여러 문제와 솔루션을 훨씬 더 빨리 찾아 낼 수있었습니다. 또한 Clang의 include-what-you-use 도구와 같이 다른 사람들이 관리 할 수 ​​없었던 Bazel과 도구를 통합하는데도 도움이되었습니다.

    넷째, 앞서 언급했듯이 다음과 같은 경우 사용 전에 메모리를 초기화하는 것과 같은 안전한 코딩 관행 에 참여하는 것이 현명합니다. 중요한 절충안이 없습니다. 아무리 신중한 엔지니어라도 가정을 조용히 무효화 할 수있는 기본 시스템의 미래 진화를 반드시 예측할 수는 없습니다.

    마지막으로 일반적으로 의학 분야 에서처럼 예방 가장 좋은 치료법입니다 . 향후 개발 가능성을 고려하고 표준화 된 인터페이스로 코딩하면 비 호환성이 발생한 후 쉽게 달성 할 수있는 것보다 더 확장 가능한 코드 디자인이 가능합니다.

    Ray를 Windows로 이식하는 것은 아직 완료되지 않았지만 상당히 지금까지 성공했으며, 경험과 솔루션을 공유하는 것이 비슷한 여정을 시작하려는 다른 개발자에게 유용한 가이드가되기를 바랍니다.

    답글 남기기

    이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다