Filedescriptor out of range in select

3409 단어
오늘 저녁에 어떤 학우가 나에게 내부 네트워크 프로토콜 패키지stpclient의 버그를 보고했는데 다음과 같다.
File "/data/apps/column-web/eggs/snow-3.2.1-py2.7.egg/snow/client.py", line 146, in service
  stp_response = self._client.call(stp_request.argv)
File "/data/apps/column-web/eggs/snow-3.2.1-py2.7.egg/snow/client.py", line 51, in call
  self.send_request(request)
File "/data/apps/column-web/eggs/stpclient2-0.0.15-py2.7.egg/stpclient2/client.py", line 221, in send_request
  self._timeout)
ValueError: filedescriptor out of range in select()

이 이상은 이미 여러 차례 발생했기 때문에 나는 원인을 분명히 하기로 결정했다.

원인을 찾다


우선, 항간에 떠도는 버전은 select에서 감청한 fd의 개수가 1024를 초과할 때 이 이상이 발생한다. 내가 분석한 결과 이 이상은 stp의 클라이언트가 던진 것이다. 이 버전의 클라이언트는 내가 쓴 것이기 때문에 1024개의 fd를 초과할 수 없다는 것을 나는 똑똑히 알고 있다. 동시에 select당할 수 없다. 그러면 문제가 생겼다. 도대체 무엇 때문일까?
이때 생각나는 문제는 바로 동네 버전에 문제가 있다는 것이다. 그래서 먼저 내가 봤는데 select 시스템 호출 중의 설명은 다음과 같다.
An fd_set is a fixed size buffer. Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined behavior. Moreover, POSIX requires fd to be a valid file descriptor.
select의 notes에 다음과 같이 쓰여 있습니다.
fd_set은 fd가 음수이거나 FD를 초과하는 고정 크기 버퍼입니다.SETSIZE 시 undefined behavior 가 생성됩니다.POSIX 요구 fd 는 합법적 인 fd 입니다
이런 면에서 보면 동네 소문과는 차이가 있다. 동시에 감청하는 fd가 FD를 초과하는 것은 아니다SETSIZE에 문제가 있을 수 있습니다. fd 크기가 FD를 초과합니다.SETSIZE에 문제가 생길 수 있습니다, FDSETSIZE는 select에 정의되어 있습니다.h의 크기는 1024(문제가 생긴 기계에서)이기 때문에 fd 크기가 1024를 초과하면 문제가 있을 수 있습니다.
/usr/include/linux/posix_types.h #undef __FD_SETSIZE #define __FD_SETSIZE 1024
그러나 여기까지는 우리가 본 그 이상이 어떻게 던져졌는지 알 수 없었다
ValueError: filedescriptor out of range in select()
분명히python에서 던진 이상입니다. 시스템 호출은 undefined behavior가 발생한다고 말할 뿐입니다.
이때python의socket모듈을 따라select모듈의python 소스 코드를 찾았습니다. 우리는 다음과 같은 몇 줄을 발견했습니다.
Modules/selectmodule.c#if defined(_MSC_VER) max = 0;/* not used for Win32/#else/!_MSC_VER/if (!_PyIsSelectable_fd(v)) { PyErr_SetString(PyExc_ValueError, "filedescriptor out of range in select()"); goto finally; } if (v >= max) max = v; #endif/_MSC_VER */
그리고 우리는 호출_PyIsSelectable_fd을false로 되돌릴 때 우리 위에 있는 이상이 발생하는 것을 발견했다. 다음에 _PyIsSelectable_fd의 실현을 살펴보자.
Include/fileobject.h #ifdef HAVE_SELECT #define _PyIsSelectable_fd(FD) (((FD) >= 0) && ((FD) < FD_SETSIZE)) #else #define _PyIsSelectable_fd(FD) (1) #endif/* HAVE_SELECT */
POSIX에서 select 시스템이 전송된 fd를 호출하는 데 합법적이어야 한다고 규정했기 때문에 호출자가 fd를 검출해야 하기 때문에python에서 fd가 마이너스일 경우 또는 FD 를 초과해야 한다SERSIZE는 비합법적인 것으로 간주되어 던진다ValueError그러면 지금 문제가 명확해졌습니다. select 호출을 실행할 때 1024보다 크거나 0보다 작은 fd가 전송되어 위의 이상이 발생했기 때문입니다. 다음 문제는 1024보다 큰 fd가 왜 발생했는지 찾아야 합니다.

현상을 해석하다


기초 구조의 학우들을 통해 알 수 있듯이 우리는 프로세스가 가장 많이 열릴 수 있는 프로세스 수를 조정했다. 슈퍼바이저 안의 모든 프로세스가 가장 많이 열릴 수 있는 fd 개수는 655360이기 때문에 fd가 1024를 초과할 가능성이 충분히 있다.이 이상은 웹 기기에 나타난다. 일부zone의 서비스가 짧은 링크를 사용하기 때문에 단일 프로세스가 가지고 있는 fd의 수가 1024를 초과할 수 있다.
이로써 이 문제는 합리적인 해석을 얻은 셈이다. 짧은 링크를 대량으로 사용하기 때문에 단일 프로세스의 fd 개수가 높아지고 1024제한을 초과하여 최초의 이상이 생겼다.

해결 방법

  • 이 값은 내부 핵에 정의되어 있기 때문에 현재 방안이 변하지 않는 전제에서 이 문제를 해결하려면 Linux-kernel을 다시 컴파일하여 이 값을 높여야 한다
  • stpclient의 클라이언트를 수정하고 epoll을 사용하여 비교적 오래된 select를 대체한다. 당시에 select를 사용한 이유는 fd개수가 적고 성능에 문제가 없으며 다른 플랫폼에서도 지원받을 수 있기 때문이다
  • 좋은 웹페이지 즐겨찾기