nodejs의 오류 처리 과정 기록

본고는 연결 오류 ECONNREFUSED를 예로 삼아 nodejs가 오류를 처리하는 과정을 살펴본다.만약 우리가 아래의 코드를 가지고 있다고 가정하면

1.  const net = require('net');  
2.  net.connect({port: 9999})
만약 이 컴퓨터에 9999 포트를 감청하지 않았다면, 우리는 아래의 출력을 얻을 수 있을 것이다.

1.  events.js:170  
2.        throw er; // Unhandled 'error' event  
3.        ^  
4.    
5.  Error: connect ECONNREFUSED 127.0.0.1:9999  
6.      at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1088:14)  
7.  Emitted 'error' event at:  
8.      at emitErrorNT (internal/streams/destroy.js:91:8)  
9.      at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)  
10.     at processTicksAndRejections (internal/process/task_queues.js:81:17)
연결의 호출 절차를 간단히 봅시다.

1.  const req = new TCPConnectWrap();  
2.  req.oncomplete = afterConnect;  
3.  req.address = address;  
4.  req.port = port;  
5.  req.localAddress = localAddress;  
6.  req.localPort = localPort;  
7.  //    
8.  err = self._handle.connect(req, address, port);
이어서 C++층connect의 논리를 보도록 하겠습니다.

1.  err = req_wrap->Dispatch(uv_tcp_connect,  
2.                               &wrap->handle_,  
3.                               reinterpret_cast<const sockaddr*>(&addr),  
4.                               AfterConnect);
C++ 레이어에서 Libuv의 uv_ 직접 호출tcp_connect, 콜백 설정은 AfterConnect입니다.이어서 우리는 libuv의 실현을 보았다.

1.  do {  
2.      errno = 0;  
3.      //    
4.      r = connect(uv__stream_fd(handle), addr, addrlen);  
5.    } while (r == -1 && errno == EINTR);  
6.    //  ,   
7.    if (r == -1 && errno != 0) {  
8.      //  , , ,   
9.      if (errno == EINPROGRESS)  
10.       ; /* not an error */  
11.     else if (errno == ECONNREFUSED)  
12.       //    
13.       handle->delayed_error = UV__ERR(ECONNREFUSED);  
14.     else  
15.       return UV__ERR(errno);  
16.   }  
17.   uv__req_init(handle->loop, req, UV_CONNECT);  
18.   req->cb = cb;  
19.   req->handle = (uv_stream_t*) handle;  
20.   QUEUE_INIT(&req->queue);  
21.   //  handle,   
22.   handle->connect_req = req;  
23.   uv__io_start(handle->loop, &handle->io_watcher, POLLOUT);
Libuv가 비동기적으로 운영체제를 호출하고 리퀘스트를 handle에 마운트하고 쓰기 가능한 이벤트를 등록하는 것을 보았습니다. 연결이 실패할 때 uvstream_io 콜백, Libuv 처리(uv stream_io)를 살펴보겠습니다.

1.  getsockopt(uv__stream_fd(stream),  
2.                 SOL_SOCKET,  
3.                 SO_ERROR,  
4.                 &error,  
5.                 &errorsize);  
6.  error = UV__ERR(error);  
7.  if (req->cb)  
8.      req->cb(req, error);
오류 정보를 가져온 후 C++ 레이어에 대한 AfterConnect를 콜백합니다.

1.  Local<Value> argv[5] = {  
2.     Integer::New(env->isolate(), status),  
3.     wrap->object(),  
4.     req_wrap->object(),  
5.     Boolean::New(env->isolate(), readable),  
6.     Boolean::New(env->isolate(), writable)  
7.   };  
8.    
9.   req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);
이어서 JS 레이어의 oncomplete 리셋을 호출합니다.

1.  const ex = exceptionWithHostPort(status,  
2.                                   'connect',  
3.                                   req.address,  
4.                                   req.port,  
5.                                   details);  
6.  if (details) {  
7.    ex.localAddress = req.localAddress;  
8.    ex.localPort = req.localPort;  
9.  }  
10. //  socket  
11. self.destroy(ex);
exception With HostPort 구조 오류 정보를 삭제한 다음 socket을 삭제하고 ex를 매개 변수로 error 이벤트를 터치합니다.uvException With HostPort의 구현을 살펴보겠습니다.

1.  function uvExceptionWithHostPort(err, syscall, address, port) {  
2.    const [ code, uvmsg ] = uvErrmapGet(err) || uvUnmappedError;  
3.    const message = `${syscall} $[code]: ${uvmsg}`;  
4.    let details = '';  
5.    
6.    if (port && port > 0) {  
7.      details = ` ${address}:${port}`;  
8.    } else if (address) {  
9.      details = ` ${address}`;  
10.   }  
11.   const tmpLimit = Error.stackTraceLimit;  
12.   Error.stackTraceLimit = 0;  
13.   const ex = new Error(`${message}${details}`);  
14.   Error.stackTraceLimit = tmpLimit;  
15.   ex.code = code;  
16.   ex.errno = err;  
17.   ex.syscall = syscall;  
18.   ex.address = address;  
19.   if (port) {  
20.     ex.port = port;  
21.   }  
22.   //  uvExceptionWithHostPort, stack ex   
23.   Error.captureStackTrace(ex, excludedStackFn || uvExceptionWithHostPort);  
24.   return ex;  
25. }
우리는 오류 정보를 주로 uvErrmapGet을 통해 얻는 것을 보았다

1.  function uvErrmapGet(name) {  
2.    uvBinding = lazyUv();  
3.    if (!uvBinding.errmap) {  
4.      uvBinding.errmap = uvBinding.getErrorMap();  
5.    }  
6.    return uvBinding.errmap.get(name);  
7.  }  
8.    
9.  function lazyUv() {  
10.   if (!uvBinding) {  
11.     uvBinding = internalBinding('uv');  
12.   }  
13.   return uvBinding;  
14. }
계속 아래를 보면 uvErrmapGet은 C++ 층의 uv 모듈의 getErrorMap을 호출합니다.

1.  void GetErrMap(const FunctionCallbackInfo<Value>& args) {  
2.    Environment* env = Environment::GetCurrent(args);  
3.    Isolate* isolate = env->isolate();  
4.    Local<Context> context = env->context();  
5.    
6.    Local<Map> err_map = Map::New(isolate);  
7.    //  per_process::uv_errors_map   
8.    size_t errors_len = arraysize(per_process::uv_errors_map);  
9.    //    
10.   for (size_t i = 0; i < errors_len; ++i) {  
11.      // map  uv_errors_map value, name message
12.     const auto& error = per_process::uv_errors_map[i];  
13.     Local<Value> arr[] = {OneByteString(isolate, error.name),  
14.                           OneByteString(isolate, error.message)}; 
15.     if (err_map  
16.             ->Set(context,  
17.                   Integer::New(isolate, error.value),  
18.                   Array::New(isolate, arr, arraysize(arr)))  
19.             .IsEmpty()) {  
20.       return;  
21.     }  
22.   }  
23.   
24.   args.GetReturnValue().Set(err_map);  
25. }
오류 정보가 존재하는 것을 보았습니다per_process::uv_errors_map에서 uv_errors_맵의 정의입니다.

1.  struct UVError {
2.    int value;
3.    const char* name;
4.    const char* message;
5.  };
6.  
7.  static const struct UVError uv_errors_map[] = {  
8.  #define V(name, message) {UV_##name, #name, message},  
9.      UV_ERRNO_MAP(V)  
10. #undef V  
11. };
UV_ERRNO_맵 매크로가 확장되면 다음과 같습니다.

1.  {UV_E2BIG, "E2BIG", "argument list too long"},  
2.  {UV_EACCES, "EACCES", "permission denied"},  
3.  {UV_EADDRINUSE, "EADDRINUSE", "address already in use"},  
4.  ……
그래서 JS 레이어로 내보낸 결과는 다음과 같습니다.

1.  {  
2.    //  , Libuv , 
3.    UV_ECONNREFUSED: ["ECONNREFUSED", "connection refused"],    
4.    UV_ECONNRESET: ["ECONNRESET", "connection reset by peer"]   
5.    ...   
6.  }
Node.js는 마지막에 이 정보를 조립하여 호출자에게 되돌려 줍니다.이것이 바로 우리가 출력한 잘못된 정보다.그럼 왜 ECONN REFUSED일까요?우리는 운영체제가 이 오류 코드에 대한 논리를 좀 봅시다.

1.  static void tcp_reset(struct sock *sk)  
2.  {  
3.      switch (sk->sk_state) {  
4.          case TCP_SYN_SENT:  
5.              sk->sk_err = ECONNREFUSED;  
6.              break;  
7.           // ...
8.      }  
9.    
10. }
운영체제가 이 socket에 보내진rst 패키지를 받았을 때 tcp_reset, 우리는 socket이syn 패키지를 보내서ack를 기다리고 있을 때fin 패키지를 받으면 ECONNREFUSED로 오류 코드를 설정하는 것을 보았습니다.우리가 출력한 것이 바로 이 오류 코드다.
총결산
이 nodejs의 오류 처리 과정에 대한 기록을 소개합니다. 더 많은 nodejs 오류 처리 내용은 저희 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보십시오. 앞으로 많은 응원 부탁드립니다!

좋은 웹페이지 즐겨찾기