Contents
- Proactor - An Object Behavioral Pattern for Demultiplexing and Dispatching Handlers for Asynchronous Events
- 원본링크 :
http://www.cs.wustl.edu/~schmidt/PDF/proactor.pdf
- 원저자 : Irfan Pyarali, Tim Harrison, Douglas C. Schmidt, Thomas D. Jordan
- proactor에 대한 그나마 구할 수 있는 유일한 논문입니다. =_=a 나름대로 번역하기로 했습니다. 원본이 PDF라 옮기는데 장난아닙니다. -_-;;;
1 개요 #
현대의 운영체계들은 동시실행에 기초한 어플리케이션(서버)를 개발하는 것을 지원하기 위해 여러가지 매커니즘들을 제공한다. 동기적 멀티쓰레딩은 여러 동작이 동시다발적으로 실행되는 어플리케이션을 개발하는데 있어서 인기있는 매커니즘이다. 그렇지만 쓰레드는 종종 높은 성능 과부하를 가질 때가 있고, 동기화 패턴과 법칙들에 대한 깊은 지식을 요구한다. 그러므로, 여러 운영체계들은 동시실행 처리에 있어서 멀티쓰레딩의 과부하나 복잡도의 대부분을 완화시키는 장점이 있는 비동기적 매커니즘을 지원한다.
이 논문에서 제시하는 proactor 패턴은 운영체계에 의해 제공되는 비동기 매커니즘들을 효율적으로 다루는 어플리케이션과 시스템을 구축하는 법에 대해서 설명하고 있다. 어플리케이션이 비동기적인 작업을 수행할 때, 운영체계는 어플리케이션을 대신하여 작업을 실행한다. 이것은 어플리케이션으로 하여금 필요한 만큼의 수의 쓰레드를 가질 필요없이 동시에 동작하는 다수의 동작을 실행할 수 있게 해준다. 따라서, proactor 패턴은 서버 프로그래밍을 단순화 시켜주며, 비동기 처리를 위한 운영체계의 지원에 의존하고 보다 적은 쓰레드를 요구하게 됨으로써 성능을 증진시켜주게 된다.
2 취지 #
proactor 패턴은 비동기 이벤트들의 완료시점에 실행되는 다중 이벤트 핸들러들의 디스패칭과 디멀티플랙싱을 지원한다. 이 패턴은 완료 이벤트들의 디멀티플랙싱을 통합하고 그에 알맞은 이벤트 핸들러들을 디스패칭하는 것을 지원함으로써 비동기기반의 어플리케이션 개발을 단순화해준다.
3.1 고성능 서버의 의미과 능력 #
동기적인 방식의 멀티쓰레드 혹은 반응적인(reactive) 프로그래밍상에서 제약없이 동시처리방식으로 작업들을 실행시키려 하는데 성능상의 잇점이 요구될 때에는 proactor 패턴을 사용한다. 이 잇점들을 설명하기 위해서 동시처리방식으로 다중의 처리를 실행할 필요가 있는 네트워크 어플리케이션을 고려하자. 예를 들자면, 고성능의 웹서버는 반드시 여러개의 클라이언트 1, 2로부터 보내어진 HTTP 요청들을 동시에 처리해야만 한다. "Figure 1"은 웹브라우져들과 웹서버사이의 전형적인 상호작용관계를 보여준다. 사용자가 브라우져에게 URL을 열도록 지시하면, 브라우져는 HTTP GET 요청을 웹 서버로 보낸다. 요청을 접수하면 서버는 요청을 파싱하고 적법한지 검사한 후, 지정된 화일(들)을 브라우져로 되돌려 보낸다.

고성능의 웹서버가 개발되기 위해서는 다음 능력들을 가져야한다:
- 동시처리(Concurrency); 서버는 동시에 여러개의 클라이언트 요청을 수행해야만 한다.
- 효율성(Efficiency); 서버는 지연(latency)을 최소화해야하고, 대역폭을 최대로 활용해야 하며, 불필요하게 CPU(들)을 동작시키는 것을 피해야한다.
- 프로그래밍 단순화(Programming simplicity); 서버의 디자인은 효율적인 동시처리에 대한 운영 전략의 적용을 단순화할 수 있어야 한다.
- 적응성(Adaptability); 신규 혹은 개선된 트랜스포트 프로토콜(HTTP 1.1과 같은)을 지원하는데 있어서 최소한의 관리 비용이 들도록 해야한다.
3.2 전통적인 동시처리방식 서버모델들의 일반적인 덫과 함정들 #
동기화된 멀티쓰레딩과 reactive한 프로그래밍은 동시처리를 구현하는 일반적인 방법이다. 이 장은 이 프로그래밍 모델들에 대한 결점들을 설명한다.
3.2.1 다중 동기화 쓰레드를 통한 동시처리 #
아마도 대부분의 동시처리방식의 웹서버를 구현하는 직관적인 방법은 동기적인 멀티쓰레딩 방식이다. 이 모델에서는 다중 서버 쓰레드들이 동시에 여러 클라이언트로 부터 HTTP GET 요청을 처리한다. 각 쓰레드는 접속 구축을 실행하고, HTTP 요청을 읽고, 요청을 파싱하며, 화일 전송 처리를 동기적으로 수행한다. 결과적으로 각 처리는 해당 처리가 완료될 때 까지 블럭당한다.
동기화된 쓰레딩 방식의 주된 잇점은 어플리케이션 코드의 단순함이다. 특별한 경우, 클라이언트 A의 요청을 서비스하기위해 웹서버에 의해 실행되는 처리들은 클라이언트 B의 요청을 서비스하기 위한 처리와는 대부분 독립적이다. 따라서, 쓰레드간에 공유되는 상태들의 양이 적기 때문에 별도의 쓰레드상에서 서로 다른 요청들을 서비스하는 것이 쉬운 것이다. (이것은 동기화의 필요성을 최소화하는 요인이다) 게다가, 별도의 쓰레드상에서 어플리케이션 로직을 실행하는 것은 개발자로 하여금 순차적인 명령들과 블록킹 처리를 다루는 것을 허용한다.

"Figure 2"는 어떻게 동기적인 쓰레드를 사용하여 디자인된 웹서버가 여러개의 클라이언트들을 동시실행방식으로 처리할 수 있는지 보여준다. 이 figure는 Sync Acceptor가 동기적으로 네트워크 접속을 accept 처리하기위한 서버측 구조를 은폐하고(encapsulate)있다는 것을 보여준다. "연결 1개당 쓰레드 1개" 방식을 사용해서 HTTP GET 요청을 서비스하기위한 각각의 쓰레드들의 실행단계는 다음과 같이 요약될 수 있다:
- 각 쓰레드는 accept()함수실행시 클라이언트 접속요청이 올때까지 동기적으로 블록당한다.
- 클라이언트가 서버에 연결되면, 접속이 accept된다. (블럭이 풀린다)
- 새로 접속된 클라이언트의 HTTP 요청이 동기적으로 네트워크 연결을 통하여 읽혀진다.
- 요청을 파싱한다.
- 요청된 화일을 동기적으로 읽는다.
- 화일의 내용이 동기적으로 클라이언트에게 전송된다.
- 쓰레딩 정책이 동시처리 정책과 강하게 연관되어있다. : 이 구조는 각 연결된 클라이언트들을 위한 개별적인 전담 쓰레드를 필요로 한다. 동시처리방식의 어플리케이션은 동시에 서비스되는 클라이언트의 수보다는 사용가능한 자원(쓰레드 풀링을 통한 CPU의 수와 같은 것)에 쓰레딩 전략을 맞추는 것에 의해 보다 더 최적화될 수 있다.
- 동기화의 복잡도 증가 : 쓰레드 처리는 - 서버의 공유 자원들(캐쉬된 화일이나 웹 페이지의 히트수 기록등등)에 대한 억세스를 직렬화하기위해서 필요한 - 동기화 체계에 대한 복잡도를 증가시킨다.
- 성능상의 과부하 증가 : 쓰레드 처리는 컨택스트 스위칭, 동기화, CPU간의 데이타 이동등에 기인하여 과부하상태로 실행될 수 있다.;
- 비호환성: 쓰레드는 모든 운영체계에 가능하지 않을 수도 있다. 게다가, 운영체계는 선점형 그리고 비선점형 쓰레드의 견지에서 보았을 경우 상당히 차이가 있다. 이런 이유로, 운영체계 플렛폼에 상관없이 동일하게 동작하는 다중 쓰레드방식의 서버를 만드는 것은 여려운 일이다.
3.2.2 반응적(reactive) 이벤트 디스패칭을 통한 동시처리 #
또다른 동기적방식의 웹서버를 구현하는 일반적인 방법은 반응적(reactive) 이벤트 디스패칭 모델을 사용하는 것이다. reactor 패턴은 어떻게 어플리케이션이 Initiation Dispatcher를 사용하여 이벤트 핸들러를 등록할 수 있는지 보여준다. Initiation Dispatcher는 블록킹 없이 명령이 입회(initiate)가능할 경우 그에 알맞는 이벤트 핸들러를 알려준다.
The Initiation Dispatcher notifies the Event Handler when it is possible to initiate an operation without blocking.
싱글쓰레드 기반의 동시처리 방식 웹서버는 reactive 이벤트 디스패칭 모델을 사용할 수 있다. (이 모델은 reactor가 알맞은 명령이 들어왔음을 알려줄 때 까지 이벤트 루프상에서 기다리는 구조를 가진다.)
웹서버상의 reactive 명령에 대한 예는 Initiation Dispatcher을 사용한 acceptor의 등록작업이다. 데이타가 네트워크 연결을 통해서 도착하면, 디스패쳐는 acceptor를 콜백한다. acceptor는 네트워크 연결을 수락하고 HTTP 핸들러를 생성한다. 그런 다음 이 HTTP 핸들러는 웹서버의 싱글쓰레드 제어하에서 방금 진행되는 연결로 전송되어온 URL 요청을 처리하기 위해 reactor에 등록된다.
figure 3과 4는 반응적 이벤트 디스페칭을 사용하여 디자인된 웹서버가 여러개의 클라이언트를 어떻게 다루는지를 보여준다. figure 3는 웹서버로 클라이언트가 접속할때 밟게되는 단계를 보여주며, figure 4는 웹서버가 어떻게 클라이언트 요청을 처리하는지 보여준다.

figure 3의 단계는 다음과 같이 요약될 수 있다:
- 웹서버는 신규 접속을 accept처리하기 위한 Initiation Dispatcher에 acceptor를 등록한다. (The Web Server registers an Acceptor with the Initiation Dispatcher to accept new connections.)
- 웹서버는 Initiation Dispatcher의 이벤트루프를 동작시킨다.
- 클라이언트가 웹서버에 접속한다.
- acceptor가 신규 접속의 발생여부를 Initiation Dispatcher에게 알려주고 acceptor는 신규 접속을 받아들인다.
- acceptor는 신규 클라이언트에 서비스하기위해 HTTP 핸들러를 생성한다.
- HTTP 핸들러는 클라이언트의 요청 데이타를 읽기위해 Initiation Dispatcher에 접속정보를 등록한다. (다시말하면, 접속상태를 "읽기대기"모드로 설정한다.)
- HTTP 핸들러 서비스는 신규 클라이언트로부터의 요청에 따라 서비스를 시작한다.

그림 4는 reactive 웹서버가 HTTP GET 요청을 서비스하는 단계를 보여준다. 그 과정은 다음과 같다:
- 클라이언트는 HTTP GET 요청을 전송한다.
- 클라이언트의 요청 데이타가 서버에 도착하였을때 Initiation Dispatcher는 HTTP 핸들러에게 그 사실을 알려준다.
- 요청 데이타가 비블록상태로 읽혀진다. (이것은 읽기명령이 즉시 수행을 끝내지 못했을 경우 EWOULDBLOCK을 반환하는 상태를 말한다.) HTTP 요청데이타가 완전히 읽혀질때까지 2번과 3번단계를 반복한다.
- HTTP 핸들러는 HTTP 요청을 파싱한다.
- 요청된 화일을 동기적으로 화일 시스템으로 부터 읽는다.
- HTTP 핸들러는 데이타를 보내기위해 Initiation Dispatcher에 연결정보를 등록한다. (다시 말하면, 접속상태가 쓰기대기상태로 된다.)
- Initiation Dispatcher는 TCP 접속이 쓰기모드 상태라는 것을 HTTP 핸들러에게 알려준다.
- 요청 데이타가 클라이언트에게 비블록상태로 전송되어진다. (이것은 쓰기명령이 즉시 수행을 끝내지 못했을 경우 EWOULDBLOCK을 반환하는 상태를 말한다.) HTTP 요청데이타가 완전히 전송되어질때까지 7번과 8번단계를 반복한다.
reactive 모델의 주된 장점은 이식성, coarse-grained 동시처리 제어에 따른 낮은 과부하 (다시말하면, 싱글스레드 방식은 동기화나 컨텍스트 스위칭이 요구되지 않는다.), 디스페칭 체계로 부터 어플리케이션 로직을 분리함으로서 얻을수 있는 모듈화의 잇점들을 들 수 있다. 그럼에도 불구하고, 이 방식은 다음과 같은 단점들을 가지고 있다:
- 프로그램 복잡도의 증가 : 앞에서 언급했듯이, 서버가 특정 클라이언트에 대해 블록당하지 않고 서비스를 실행하려면 프로그래머는 복잡한 로직을 작성해야만 한다.
- 멀티쓰레딩에 대한 운영체계 지원의 부족 : 대부분의 운영체계들은 reactive 디스페칭 모델을 select() 함수로 구현한다[7]. 어쨌거나, select()는 같은 descriptor set에서 한개이상의 쓰레드의 사용을 허용하지 않는다. 이것은 reactive 모델이 고성능의 서버의 제작에는 맞지 않다는 의미가 된다. (하드웨어 병렬처리를 효율적으로 이용하려면 멀티쓰레드의 사용은 필수적이기 때문이다.)
- 실행가능한 task들의 스케줄링: 선점형 쓰레드를 지원하는 동기방식의 멀티쓰레딩 구조하에서는, 설치된 CPU를 가지고 실행가능한 쓰레드들을 스케줄하고 시분할하여 제어하는 것은 운영체계의 역할이라고 할 수 있다. 이 스케줄링은 어플리케이션에서 한개의 쓰레드만이 존재하는 reactive 구조에서는 사용될 수 없다. 그러므로, 시스템 개발자는 웹서버에 연결된 모든 클라이언트들의 요청을 처리하는데 있어서 이 1개의 쓰레드의 실행단위를 주의깊게 시분할할 필요가 있다. 이것은 짧은 주기로 비블록 명령들을 실행함으로서 구현될 수 있다.
3.3 해결책 : proactive 처리를 통한 동시처리 #
OS 플렛폼이 비동기 명령들을 지원할 경우, 고성능의 웹서버를 구현하는 효율적이고 편리한 방법은 proactive 이벤트 디스페칭을 사용하는 것이다. proactive 이벤트 디스페칭을 사용하여디자인된 웹 서버는 한개이상의 쓰레드를 제어하여 비동기명령의 완료여부를 다루는 것이 가능하다. 따라서, proactor 패턴은 완료 이벤트 디멀티플렉싱과 이벤트 핸들러 디스페칭을 통합함으로써 비동기방식의 웹서버 구조를 단순화시킨다.
비동기 웹서버는 운영체계에 처음에 비동기명령을 시동할 때와 명령이 완료했을 때를 알려주기 위한 완료 발송자(completion dispatcher)에 콜백함수를 등록하기 위해 proactor 패턴을 사용할 수 있다. 운영체계는 이때 웹서버입장에서 명령을 수행하며 순차적으로 운영체계 내의 잘 알려진 곳에 결과를 적재(queue)한다. 완료 발송자(Completion Dispatcher)는 완료 알림메세지들을 뽑아내고(dequeue), 어플리케이션 동작위주의 웹서버 코드를 담은 알맞은 콜백함수를 실행하는 역할을 담당한다.
그림 5와 6은 proactor 패턴방식의 이벤트 디스패칭을 사용하여 디자인된 웹서버가 한개 이상의 쓰레드내에서 여러 클라이언트들을 어떻게 동시처리하는지를 보여준다.

그림 5는 클라이언트가 웹서버로 접속했을때 실행되는 단계의 순서를 보여준다.
- 웹서버는 acceptor에게 비동기 accept 처리를 초기화하도록 알려준다.
- acceptor는 운영체계의 기능을 이용하여 비동기 accept 요청을 초기화하고, 그 자신을 완료 핸들러(Completion Handler)와 완료 발송자(Completion Dispatcher)의 참조로써 넘기게 된다. (이것은 비동기 accept의 완료여부를 acceptor에게 알려주는데 사용된다.)
- 웹서버는 완료 발송자의 이벤트 루프를 실행한다.
- 클라이언트가 웹서버에 접속한다.
- 비동기 accept 명령이 완료하면, 운영체계는 완료 발송자에게 통지한다.
- 완료 발송자는 acceptor에게 통지한다.
- acceptor는 HTTP 핸들러를 생성한다.
- HTTP 핸들러는 클라이언트로 부터 전송되는 요청 데이타를 비동기적으로 읽는 작업을 초기화하고 그 자신을 완료 핸들러(Completion Handler)와 완료 발송자(Completion Dispatcher)의 참조로써 넘기게 된다. (이것은 비동기 읽기작업의 완료여부를 acceptor에게 알려주는데 사용된다.)

그림 6은 proactor 패턴을 적용한 웹서버가 HTTP GET 요청을 서비스하기위한 단계를 보여준다. 이 단계는 아래와 같다.
- 클라이언트가 HTTP GET 요청을 전송한다.
- 읽기 작업이 완료되면 운영체계는 완료 발송자에게 통지한다.
- 완료 발송자는 HTTP 핸들러에게 통지한다. (2단계와 3단계는 전체 요청 메세지가 모두 전송받아질 때까지 반복하게 된다.)
- HTTP 핸들러는 요청 데이타를 파싱한다.
- HTTP 핸들러가 동기적으로 요청된 화일을 읽어들인다.
- HTTP 핸들러는 화일 데이타를 접속된 클라이언트로 전송하기위한 비동기 명령을 초기화한다. 그리고 그 자신을 완료 핸들러(Completion Handler)와 완료 발송자(Completion Dispatcher)의 참조로써 넘기게 된다. 이것은 비동기 화일전송작업의 완료여부를 HTTP 핸들러에게 통지하는데 사용된다.
- 전송작업이 완료되면 운영체계는 완료 발송자에게 통지한다.
- 이때 완료 발송자는 완료 핸들러에게 통지한다. (6~8단계는 화일이 모두 전송될때까지 반복된다.)
proactor 모델의 주요 단점은 reactor 모델보다 프로그래밍 로직이 보다 더 복잡해질 수 있다는 것 이다. 게다가, 비동기 명령들은 가끔 예측하기 힘들고 반복되지않는 실행순서를 가지는 까닭에 proactor 패턴은 실행 분석과 디버그하기가 다소 어렵다. 7장은 비동기 어플리케이션을 단순화시켜주는 (비동기 완료 토큰[8]과 같은) 다른 패턴들을 적용시키는 방법에 대해 설명하고 있다.
4 적용해야 할 경우 #
proactor 패턴은 다음과 같은 조건을 한개 이상 만족할 때 사용하기를 권장한다.
- 호출되는 쓰레드를 블록하지 않고 한개 이상의 비동기 명령들을 실행할 필요가 있을 때.
- 비동기 명령들이 완료될 때를 통지받아야 할 때.
- 입출력 모델에 독립적으로 다양한 동시처리 전략이 요구될 때.
- 어플리케이션 독립적으로 구현된 하부구조로부터 어플리케이션 의존적인 로직을 흡수할 경우 잇점이 많을 때.
- 멀티쓰레딩 방식 혹은 reactor 디스패칭 방식으로는 성능이 기대한 것보다 낮거나 비효율적인 경우.
5 구조와 구성요소들 #
proactor 패턴의 구조는 figure 7에 OMT 표기법으로 그려져있다.

proactor 패턴의 핵심 구성요소는 다음과 같다:
- Proactive Initiator (웹서버 어플리케이션의 주 쓰레드) : A Proactive Initiator is any entity in the application that initiates an Asynchronous Operation. The Proactive Initiator registers a Completion Handler and a Completion Dispatcher with a Asynchronous Operation Processor, which notifies it when the operation completes.
- 완료(Completion) 핸들러 (the Acceptor and HTTP Handler): The Proactor pattern uses Completion Handler interfaces that are implemented by the application for Asynchronous Operation completion notification.
- 비동기 명령 (the methods Async Read, Async Write, and Async Accept) : Asynchronous Operations are used to execute requests (such as I/O and timer operations) on behalf of applications. When applications invoke Asynchronous Operations, the operations are performed without borrowing the application's thread of control. Therefore, from the application’s perspective, the operations are performed asynchronously. When Asynchronous Operations complete, the Asynchronous Operation Processor delegates application notifications to a Completion Dispatcher.
- 비동기 명령 프로세서 (the Operating System) : Asynchronous Operations are run to completion by the Asynchronous Operation Processor. This component is typically implemented by the OS.
- 완료(Completion) 디스패쳐 (the Notification Queue) : The Completion Dispatcher is responsible for calling back to the application's Completion Handlers when Asynchronous Operations complete. When the Asynchronous Operation
6 Collaborations #

There are several well-defined steps that occur for all Asynchronous Operations. At a high level of abstraction, applications initiate operations asynchronously and are notified when the operations complete. Figure 8 shows the following interactions that must occur between the pattern participants:
- Proactive Initiators initiates operation: To perform asynchronous operations, the application initiates the operation on the Asynchronous Operation Processor. For instance, a Web server might ask the OS to transmit a file over the network using a particular socket connection. To request such an operation, the Web server must specify which file and network connection to use. Moreover, the Web server must specify (1) which Completion Handler to notify when the operation completes and (2) which Completion Dispatcher should perform the callback once the file is transmitted.
- Asynchronous Operation Processor performs operation: When the application invokes operations on the Asynchronous Operation Processor it runs them asynchronously with respect to other application operations. Modern operating systems (such as Solaris and Windows NT) provide asynchronous I/O subsystems within the kernel.
- The Asynchronous Operation Processor notifies the Completion Dispatcher: When operations complete, the Asynchronous Operation Processor retrieves the Completion Handler and Completion Dispatcher that were specified when the operation was initiated. The Asynchronous Operation Processor then passes the Completion Dispatcher the result of the Asynchronous Operation and the Completion Handler to call back. For instance, if a file was transmitted asynchronously, the Asynchronous Operation Processor may report the completion status (such as success or failure), as well as the number of bytes written to the network connection.
- Completion Dispatcher notifies the application: The Completion Dispatcher calls the completion hook on the Completion Handler, passing it any completion data specified by the application. For instance, if an asynchronous read completes, the Completion Handler will typically be passed a pointer to the newly arrived data.
7.1 장점 #
proactor 패턴은 다음과 같은 장점들을 가지고 있다:
- 고려사항에 대한 구분이 보다더 명확함: Proactor 패턴은 어플리케이션과는 독립적인 비동기 체계들을 어플리케이션 고유의 기능과 분리시켜준다. The application-independent mechanisms become reusable components that know how to demultiplex the completion events associated with Asynchronous Operations and dispatch the appropriate callback methods defined by the Completion Handlers. Likewise, the application-specific functionality knows how to perform a particular type of service (such as HTTP processing).
- 어플리케이션 로직에 대한 이식성 증가: It improves application portability by allowing its interface to be reused independently of the underlying OS calls that perform event demultiplexing. These system calls detect and report the events that may occur simultaneously on multiple event sources. Event sources may include I/O ports, timers, synchronization objects, signals, etc. On real-time POSIX platforms, the asynchronous I/O functions are provided by the aio family of APIs [9]. In Windows NT, I/O completion ports and overlapped I/O are used to implement asynchronous I/O 10.
- 완료 디스페쳐가 동시처리 체계를 은폐(encapsulate)시켜준다: A benefit of decoupling the Completion Dispatcher from the Asynchronous Operation Processor is that applications can configure Completion Dispatchers with various concurrency strategies without affecting other participants. As discussed in Section 7, the Completion Dispatcher can be configured to use several concurrency strategies including single-threaded and Thread Pool solutions.
- 쓰레딩 정책이 동시처리 정책과 중복되지 않는다.: Since the Asynchronous Operation Processor completes potentially long-running operations on behalf of Proactive Initiators, applications are not forced to spawn threads to increase concurrency. This allows an application to vary its concurrency policy independently of its threading policy. For instance, a Web server may only want to have one thread per CPU, but may want to service a higher number of clients simultaneously.
- 효율의 증가: Multithreaded operating systems perform context switches to cycle through multiple threads of control. While the time to perform a context switch remains fairly constant, the total time to cycle through a large number of threads can degrade application performance significantly if the OS context switches to an idle thread. For instance, threads may poll the OS for completion status, which is inefficient. The Proactor pattern can avoid the cost of context switching by activating only those logical threads of control that have events to process. For instance, a Web server does not need to activate an HTTP Handler if there is no pending GET request.
- 어플리케이션 동기화의 단순화: As long as Completion Handlers do not spawn additional threads of control, application logic can be written with little
7.2 단점 #
proactor 패턴은 다음과 같은 단점들을 가지고 있다:
- 디버그하기 어렵다 : proactor 패턴을 사용하여 개발된 어플리케이션은 디버그하기가 어려워질 수 있다. (이것은 뒤집어진 제어 흐름이 프레임워크 하부구조와 어플리케이션에서 정의된 핸들러상의 콜백사이를 왔다갔다하기 때문이다. 한마디로 추적하다보면 내가 어디있지? 하는 부분을 말한다.) 이 때문에 프레임워크의 실행과정 중에 "한줄한줄씩 밟아나가는 방식"의 디버그는 정말로 어려워질 수 있다. (어플리케이션 개발자들은 프레임워크의 소스 코드를 가지고 있지 않거나 이해하지 못하기 때문이다) 이것은 LEX나 YACC으로 쓰여진 컴파일러의 구문 분석기나 파서를 디버그할 때 부딛히는 문제와 비슷하다. 이런 형태의 어플리케이션에서는, 제어되는 쓰레드가 사용자 정의된 액션 루틴상에 있을때 디버깅은 수월해질 수 있다. (In these applications, debugging is straightforward when the thread of control is within the user-defined action routines.) 한번 제어 쓰레드가 유한결정오토마타(DFA : Deterministic Finite Automata)를 생성하고 반환되면, 어찌되었던간에 프로그램 로직을 따라가는 것은 힘들어진다.
- 두드러진(outstanding) 명령처리와 스케줄링 : Proactive Initiators는 비동기 명령들이 실행되는 순서를 조정할 수 없을 수도 있다. 그러므로, 비동기 명령 프로세서는 비동기 명령들의 우선순위지정과 취소기능을 지원하도록 주의깊게 제작되어야 한다.
8.1 비동기 명령 프로세서의 구현 #
The first step in implementing the Proactor pattern is build-ing the Asynchronous Operation Processor. The Asynchronous Operation Processor is responsible for executing operations asynchronously on behalf of applications. As a result, its two primary responsibilities are exporting Asynchronous Operation APIs and implementing an Asynchronous Operation Engine to do the work.
8.1.1 비동기 명령 API를 정의하기 #
The Asynchronous Operation Processor must provide an API that allows applications to request Asynchronous Operations. There are several forces to be considered when designing these APIs:
- 이식성: The APIs should not tie an application nor its Proactve Initiators to a particular platform.
- 융통성: Often, asynchronous APIs can be shared for many types of operations. For instance, asynchronous I/O operations can often be used to perform I/O on multiple mediums (such as network and files). It may be beneficial to design APIs that support such reuse.
- 콜백 : The Proactive Initiators must register a callback when the operation is invoked. A common approach to implement callbacks is to have the calling objects (clients) export an interface known by the caller (server). Therefore, Proactive Initiators must inform the Asynchronous Operation Processor which Completion Handler should be called back when an operation completes.
- 완료 디스패쳐 : Since an application may use multiple Completion Dispatchers, the Proactive Initiator also must indicate which Completion Dispatcher should perform the callback.
class Asynch_Stream
// = TITLE
// A Factory for initiating reads
// and writes asynchronously.
{
// Initializes the factory with information
// which will be used with each asynchronous
// call. <handler> is notified when the
// operation completes. The asynchronous
// operations are performed on the <handle>
// and the results of the operations are
// sent to the <Completion_Dispatcher>.
Asynch_Stream (Completion_Handler &handler, HANDLE handle, Completion_Dispatcher *);
// This starts off an asynchronous read.
// Upto <bytes_to_read> will be read and
// stored in the <message_block>.
int read (Message_Block &message_block, u_long bytes_to_read, const void *act = 0);
// This starts off an asynchronous write.
// Upto <bytes_to_write> will be written
// from the <message_block>.
int write (Message_Block &message_block, u_long bytes_to_write, const void *act = 0);
...
};
8.1.2 비동기 처리 엔진의 구현 #
The Asynchronous Operation Processor must contain a mechanism that performs the operations asynchronously. In other words, when an application thread invokes an Asynchronous Operation, the operation must be performed without borrowing the application's thread of control. Fortunately, modern operating systems provide mechanisms for Asynchronous Operations (for example, POSIX asynchronous I/O and WinNT overlapped I/O). When this is the case, implementing this part of the pattern simply requires mapping the platform APIs to the Asynchronous Operation APIs described above. If the OS platform does not provide support for Asynchronous Operations, there are several implementation techniques that can be used to build an Asynchronous Operation Engine. Perhaps the most intuitive solution is to use dedicated threads to perform the Asynchronous Operations for applications.
To implement a threaded Asynchronous Operation Engine, there are three primary steps:
- Operation invocation: Because the operation will be performed in a different thread of control from the invoking application thread, some type of thread synchronization must occur. One approach would be to spawn a thread for each operation. A more common approach is for the Asynchronous Operation Processor to control a pool of dedicated threads. This approach would require that the application thread queue the operation request before continuing with other application computations.
- Operation execution: Since the operation will be performed in a dedicated thread, it can perform “blocking” operations without directly impeding progress of the application. For instance, when providing a mechanismfor asynchronous I/O reads, the dedicated thread can block while reading from socket or file handles.
- Operation completion: When the operation completes, the application must be notified. In particular, the dedicated thread must delegate application-specific notifications to the Completion Dispatcher. This will require additional synchronization between threads.
8.2 Implementing the Completion Dispatcher #
The Completion Dispatcher calls back to the Completion Handler that is associated with the application objects when it receives operation completions from the Asynchronous Operation Processor. There are two issues involved with implementing the Completion Dispatcher: (1) implementing callbacks and (2) defining the concurrency strategy used to perform the callbacks.
8.2.1 Implementing Callbacks #
The Completion Dispatcher must implement a mechanism through which Completion Handlers are invoked. This requires Proactive Initiators to specify a callback when initiating operations. The following are common callback alternatives:
- Callback class: The Completion Handler exports an interface known by the Completion Dispatcher. The Completion Dispatcher calls back on a method in this interface when the operation completes and passes it information about the completed operation (such as the number of bytes read from the network connection).
- Function pointer: The Completion Dispatcher invokes the Completion Handler via a callback function pointer. This approach effectively breaks the knowledge dependency between the Completion Dispatcher and the Completion Handler. This has two benefits:
- The Completion Handler is not forced to export a specific interface; and
- There is no need for compile-time dependencies between the Completion Dispatcher and the Completion Handler.
- Rendezvous: The Proactive Initiator can establish an event object or a condition variable, which serves as a rendezvous between the Completion Dispatcher and the Completion Handler. This is most common when the Completion Handler is the Proactive Initiator. While the Asynchronous Operation runs to completion, the Completion Handler processes other activity. Periodically, the Completion Handler will check at the rendezvous point for completion status.
8.2.2 Defining Completion Dispatcher Concurrency Strategies #
A Completion Dispatcher will be notified by the Asynchronous Operation Processor when operations completes. At this point, the Completion Dispatcher can utilize one of the following concurrency strategies to perform the application callback:
- Dynamic-thread dispatching: A thread can be dynamically allocated for each Completion Handler by the Completion Dispatcher. Dynamic-thread dispatching can be implemented with most multi-threaded operating systems. On some platforms, this may be the least efficient technique of those listed for Completion Dispatcher implementations due to the overhead of creating and destroying thread resources.
- Post-reactive dispatching: An event object or condition variable established by the Proactive Initiator can be signaled by the Completion Dispatcher. Although polling and spawning a child thread that blocks on the event object are options, the most efficient method for Post-reactive dispatching is to register the event with a Reactor. Post-reactive dispatching can be implemented with aio suspend in POSIX real-time environments and with WaitForMultipleObjects in Win32 environ-ments.
- Call-through dispatching: The thread of control from the Asynchronous Operation Processor can be borrowed by theCompletion Dispatcher to execute the Completion Handler. This “cycle stealing” strategy can increase performance by decreasing the incidence of idle threads. In the cases where older operating systems will context switch to idle threads just to switch back out of them, this approach has a great potential of reclaiming “lost” time. Call-through dispatching can be implemented in Windows NT using the ReadFileEx and WriteFileEx Win32 functions. For example, a thread of control can use these calls to wait on a semaphore to become signaled. When it waits, the thread informs the OS that it is entering into a special state known as an “alertable wait state.” At this point, the OS can seize control of the waiting thread of control's stack and associated resources in order to execute the Completion Handler.
- Thread Pool dispatching: A pool of threads owned by the Completion Dispatcher can be used for Completion Handler execution. Each thread of control in the pool has been dynamically allocated to an available CPU. Thread pool dispatching can be implemented with Windows NT's I/O Completion Ports. When considering the applicability of the Completion Dispatcher techniques described above, consider the possible combinations of OS environments and physical hardware shown in Table 1.

If your OS only supports synchronous I/O, then refer to the Reactor pattern [5]. However, most modern operating systems support some form of asynchronous I/O. In combination A and B from Table 1, the Post-reactive approach to asynchronous I/O is probably the best, assuming you are not waiting on any semaphores or mutexes. If you are, a Call-through implementation may be more responsive. In combination C, use a Call-through approach. In combination D, use a Thread Pool approach. In practice, systematic empirical measurements are necessary to select the most appropriate alternative.
8.3 Implementing Completion Handlers #
The implementation of Completion Handlers raises the following concerns.
8.3.1 State Integrity #
A Completion Handler may need to maintain state information concerning a specific request. For instance, the OS may notify the Web Server that only part of a file was written to the network communication port. As a result, a Completion Handler may need to reissue the request until the file is fully written or the connection becomes invalid. Therefore, it must know the file that was originally specified, how many bytes are left to write, and what was the file pointer position at the start of the previous request. There is no implicit limitation that prevents Proactive Initiators from assigning multiple Asynchronous Operation requests to a single Completion Handler.
As a result, the Completion Handler must tie request-specific state information throughout the chain of completion notifications. To do this, Completion Handlers can utilize the Asynchronous Completion Token pattern [8].
8.3.2 Resource Management #
As with any multi-threaded environment, the Proactor pattern does not alleviate Completion Handlers from ensuring that access to shared resources is thread-safe. However,
a Completion Handler must not hold onto a shared resource across multiple completion notifications. If it does, it risks invoking the dining philosopher's problem 11.
This problem is the deadlock that results when a logical thread of control waits forever for a semaphore to become signaled. This is illustrated by imagining a dinner party attended by a group of philosophers. The diners are seated around a circular table with exactly one chop stick between each philosopher. When a philosopher becomes hungry, he
must obtained the chop stick to his left and to his right in order to eat. Once philosophers obtain a chop stick, they will not release it until their hunger is satisfied. If all philosophers pick up the chop stick on their right, a deadlock occurs because the chop stick on the left will never become available.
8.3.3 Preemptive Policy #
The Completion Dispatcher type determines if a Completion Handler can be preemptive while executing. When attached to Dynamic-thread and Thread Pool dispatchers, Completion Handlers are naturally preemptive. However, when tied to a Post-reactive Completion Dispatcher, Completion Handlers are not preemptive with respect to each other. When driven by a Call-through dispatcher, the Completion Handlers are not preemptive with respect to the thread-of-control that is in the alertable wait state. In general, a handler should not perform long-duration synchronous operations unless multiple completion threads are used since this will significantly decrease the overall responsiveness of the application. This risk can be alleviated by increased programming discipline. For instance, all Completion Handlers are required to act as Proactive Initiators instead of executing synchronous operations.
9 Sample Code #
This section shows howto use the Proactor pattern to develop a Web server. The example is based on the Proactor pattern implementation in the ACE framework [4]. When a client connects to the Web server, the HTTP Handler’s open method is called. The server then initializes the asynchronous I/O object with the object to call-back when the Asynchronous Operation completes (which in this case is this), the network connection for transferring the data, and the Completion Dispatcher to be used once the operation completes (proactor ). The read operation is then started asynchronously and the server returns to the event loop. The HTTP Handler::handle read stream is called back by the dispatcher when the Async read operation completes. If there is enough data, the client request is then parsed. If the entire client request has not arrived yet, another read operation is initiated asynchronously. In response to a GET request, the server memory-maps the requested file and writes the file data asynchronously to the client. The dispatcher calls back on HTTP Handler::handle write stream when the write operation completes, which frees up dynamically allocated resources. The Appendix contains two other code examples for implementing the Web server using a synchronous threaded model and a synchronous (non-blocking) reactive model.
class HTTP_Handler : public Proactor::Event_Handler
// = TITLE
// Implements the HTTP protocol
// (asynchronous version).
//
// = PATTERN PARTICIPANTS
// Proactive Initiator = HTTP_Handler
// Asynch Op = Network I/O
// Asynch Op Processor = OS
// Completion Dispatcher = Proactor
// Completion Handler = HTPP_Handler
{
public:
void open (Socket_Stream *client)
{
// Initialize state for request
request_.state_ = INCOMPLETE;
// Store reference to client.
client_ = client;
// Initialize asynch read stream
stream_.open (*this, client_->handle(), proactor_);
// Start read asynchronously.
stream_.read (request_.buffer(), request_.buffer_size());
}
// This is called by the Proactor
// when the asynch read completes
void handle_read_stream(u_long bytes_transferred)
{
if (request_.enough_data(bytes_transferred))
parse_request();
else
// Start reading asynchronously.
stream_.read(request_.buffer(), request_.buffer_size());
}
void parse_request()
{
// Switch on the HTTP command type.
switch (request_.command ()) {
// Client is requesting a file.
case HTTP_Request::GET:
// Memory map the requested file.
file_.map (request_.filename ());
// Start writing asynchronously.
stream_.write (file_.buffer (),
file_.buffer_size ());
break;
// Client is storing a file
// at the server.
case HTTP_Request::PUT:
// ...
}
}
void handle_write_stream(u_long bytes_transferred)
{
if (file_.enough_data(bytes_transferred))
// Success....
else
// Start another asynchronous write
stream_.write(file_.buffer(), file_.buffer_size());
}
private:
// Set at initialization.
Proactor *proactor_;
// Memory-mapped file_;
Mem_Map file_;
// Socket endpoint.
Socket_Stream *client_;
// HTTP Request holder
HTTP_Request request_;
// Used for Asynch I/O
Asynch_Stream stream_;
};
10 Known Uses #
The following are some widely documented uses of the Proctor pattern:
- I/O Completion Ports in Windows NT: The Windows NT operating system implements the Proactor pattern. Various Asynchronous Operations such as accepting new network connections, reading and writing to files and sockets, and transmission of files across a network connection are supported by Windows NT. The operating system is the Asynchronous Operation Processor. Results of the operations are queued up at the I/O completion port (which plays the role of the Completion Dispatcher).
- The UNIX AIO Family of Asynchronous I/O Operations: On some real-time POSIX platforms, the Proactor pattern is implemented by the aio family of APIs [9]. These OS
- ACE Proactor: The Adaptive Communications Environment (ACE) [4] implements a Proactor component that encapsulates I/O Completion Ports on Windows NT and the aio APIs on POSIX platforms. The ACE Proactor abstraction provides an OO interface to the standard C APIs supported by Windows NT. The source code for this implementation can be acquired from the ACE website at www.cs.wustl.edu/˘schmidt/ACE.html.
- Asynchronous Procedure Calls in Windows NT: Some systems (such as Windows NT) support Asynchronous Procedure Calls (APC)s. An APC is a function that executes asynchronously in the context of a particular thread. When an APC is queued to a thread, the system issues a software interrupt. The next time the thread is scheduled, it will run the APC. APCs made by operating system are called kernel-mode APCs. APCs made by an application are called user-mode APCs.
11 Related Patterns #
Figure 9 illustrates patterns that are related to the Proactor.

The Asynchronous Completion Token (ACT) pattern [8] is generally used in conjunction with the Proactor pattern. When Asynchronous Operations complete, applications may need more information than simply the notification itself to properly handle the event. The Asynchronous Completion Token pattern allows applications to efficiently associate state with the completion of Asynchronous Operations. The Proactor pattern is related to the Observer pattern 12 (where dependents are updated automatically when a single
subject changes). In the Proactor pattern, handlers are informed automatically when events from multiple sources occur. In general, the Proactor pattern is used to asynchronously demultiplex multiple sources of input to their associated event handlers, whereas an Observer is usually associated with only a single source of events.
The Proactor pattern can be considered an asynchronous variant of the synchronous Reactor pattern [5]. The Reactor pattern is responsible for demultiplexing and dispatching of multiple event handlers that are triggered when it is possible to initiate an operation synchronously without blocking. In contrast, the Proactor supports the demultiplexing and dispatching of multiple event handlers that are triggered by the completion of asynchronous events. The Active Object pattern 13 decouples method execution from method invocation. The Proactor pattern is similar because Asynchronous Operation Processors perform operations on behalf of application Proactive Initiators. That is, both patterns can be used to implement Asynchronous Operations. The Proactor pattern is often used in place of the Active Object pattern to decouple the systems concurrency policy from the threading model. A Proactor may be implemented as a Singleton 12. This is useful for centralizing event demultiplexing and completion
dispatching into a single location within an asynchronous application. The Chain of Responsibility (COR) pattern 12 decouples event handlers from event sources. The Proactor pattern is similar in its segregation of Proactive Initiators and Completion Handlers. However, in COR, the event source has no prior knowledge of which handler will be executed, if any. In Proactor, Proactive Initiators have full disclosure of the target handler. However, the two patterns can be combined by establishing a Completion
Handler that is the entry pont into a responsibility chain dynamically configured by an external factory.
12 Concluding Remarks #
The Proactor pattern embodies a powerful design paradigm that supports efficient and flexible event dispatching strategies for high-performance concurrent applications. The Proactor pattern provides the performance benefits of executing operations concurrently, without constraining the developer to synchronous multi-threaded or reactive programming.
13 References #
[1] J. Hu, I. Pyarali, and D. C. Schmidt, “Measuring the Impact of Event Dispatching and Concurrency Models on Web Server Performance Over High-speed Networks,” in Proceedings of the 2nd Global Internet Conference, IEEE, November 1997.
[2] J. Hu, I. Pyarali, and D. C. Schmidt, “Applying the Proactor Pattern to High-Performance Web Servers,” in Proceedings of the 10th International Conference on Parallel and Distributed Computing and Systems, IASTED, Oct. 1998.
[3] J. C. Mogul, “The Case for Persistent-connection HTTP,” in Proceedings of ACMSIGCOMM ’95 Conference in Computer
Communication Review, (Boston, MA, USA), pp. 299–314,
ACM Press, August 1995.
[4] D. C. Schmidt, “ACE: an Object-Oriented Framework for Developing Distributed Applications,” in Proceedings of the 6 th USENIX C++ Technical Conference, (Cambridge, Mas-sachusetts), USENIX Association, April 1994.
[5] D. C. Schmidt, “Reactor: An Object Behavioral Pattern for Concurrent Event Demultiplexing and Event Handler Dis-patching,” in Pattern Languages of Program Design (J. O.
Coplien and D. C. Schmidt, eds.), pp. 529–545, Reading, MA: Addison-Wesley, 1995.
[6] D. C. Schmidt, “Acceptor and Connector: Design Patterns for Initializing Communication Services,” in Pattern Languages of Program Design (R. Martin, F. Buschmann, and D. Riehle, eds.), Reading, MA: Addison-Wesley, 1997.
[7] M. K. McKusick, K. Bostic, M. J. Karels, and J. S. Quarter-man, The Design and Implementation of the 4.4BSD Operating System. Addison Wesley, 1996.
[8] I. Pyarali, T. H. Harrison, and D. C. Schmidt, “Asynchronous Completion Token: an Object Behavioral Pattern for Efficient Asynchronous Event Handling,” in Pattern Languages of Program Design (R. Martin, F. Buschmann, and D. Riehle, eds.), Reading, MA: Addison-Wesley, 1997.
[9] “Information Technology – Portable Operating System Interface (POSIX) – Part 1: System Application: Program Interface (API) C Language,” 1995. 10 Microsoft Developers Studio, Version 4.2 - Software Development Kit, 1996.
11 E. W. Dijkstra, “Hierarchical Ordering of Sequential Pro-cesses,” Acta Informatica, vol. 1, no. 2, pp. 115–138, 1971.
12 E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Pat-terns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
13 R. G. Lavender and D. C. Schmidt, “Active Object: an Object Behavioral Pattern for Concurrent Programming,” in Proceedings of the 2nd Annual Conference on the Pattern Languages of Programs, (Monticello, Illinois), pp. 1–7, September 1995.
13.1 A. Alternative Implementations #
This Appendix outlines the code used to develop alternatives to the Proactor pattern. Below, we examine both synchronous I/O using multi-threading and reactive I/O using
single-threading.
13.1.1 A.1 Multiple Synchronous Threads #
The following code shows how to use synchronous I/O with a pool of threads to develop a Web server. When a client con-nects to the server a thread in the pool accepts the connection and calls the open method in class HTTP Handler. The server then synchronously reads the request from the network connection. When the read operation completes,
the client request is then parsed. In response to a GET request, the server memory-maps the requested file and writes the file data synchronously to the client. Note how blocking I/O allows the Web server to follow the steps outlined in Section 2.2.1.
class HTTP_Handler
// = TITLE
// Implements the HTTP protocol
// (synchronous threaded version).
//
// = DESCRIPTION
// This class is called by a
// thread in the Thread Pool.
{
public:
void open (Socket_Stream *client)
{
HTTP_Request request;
// Store reference to client.
client_ = client;
// Synchronously read the HTTP request
// from the network connection and
// parse it.
client_->recv (request);
parse_request (request);
}
void parse_request (HTTP_Request &request)
{
// Switch on the HTTP command type.
switch (request.command ())
{
// Client is requesting a file.
case HTTP_Request::GET:
// Memory map the requested file.
Mem_Map input_file;
input_file.map (request.filename());
// Synchronously send the file
// to the client. Block until the
// file is transferred.
client_->send (input_file.data (),
input_file.size ());
break;
// Client is storing a file at
// the server.
case HTTP_Request::PUT:
// ...
}
}
private:
// Socket endpoint.
Socket_Stream *client_;
// ...
};
13.1.2 A.2 Single-threaded Reactive Event Dispatching #
The following code shows the use of the Reactor pattern to develop a Web server. When a client connects to the server, the HTTP Handler::open method is called. The server
registers the I/O handle and the object to callback (which in this case is this) when the network handle is “ready for reading.” The server returns to the event loop.
When the request data arrives at the server, the reactor calls back the HTTP Handler::handle input method. The client data is read in a non-blocking manner. If there is
enough data, the client request is parsed. If the entire client request has not yet arrived, the application returns to the re-actor event loop.
In response to a GET request, the server memory maps the requested file and registers with the reactor to be notified when the network connection becomes “ready for writing.” The reactor then calls back on HTTP Handler::handle output method when writing data to the connection would not blocking the calling thread. When all the data has been sent to the client, the network connection is closed.
class HTTP_Handler :
public Reactor::Event_Handler
// = TITLE
// Implements the HTTP protocol
// (synchronous reactive version).
//
// = DESCRIPTION
// The Event_Handler base class
// defines the hooks for
// handle_input()/handle_output().
//
// = PATTERN PARTICIPANTS
// Reactor = Reactor
// Event Handler = HTTP_Handler
{
public:
void open (Socket_Stream *client)
{
// Initialize state for request
request_.state_ = INCOMPLETE;
// Store reference to client.
client_ = client;
// Register with the reactor for reading.
reactor_->register_handler
(client_->handle (),
this,
Reactor::READ_MASK);
}
// This is called by the Reactor when
// we can read from the client handle.
void handle_input (void)
{
int result = 0;
// Non-blocking read from the network
// connection.
do
result = request_.recv (client_->handle ());
while (result != SOCKET_ERROR
&& request_.state_ == INCOMPLETE);
// No more progress possible,
// blocking will occur
if (request_.state_ == INCOMPLETE
&& errno == EWOULDBLOCK)
reactor_->register_handler
(client_->handle (),
this,
Reactor::READ_MASK);
else
// We now have the entire request
parse_request ();
}
void parse_request (void)
{
// Switch on the HTTP command type.
switch (request_.command ()) {
// Client is requesting a file.
case HTTP_Request::GET:
// Memory map the requested file.
file_.map (request_.filename ());
// Transfer the file using Reactive I/O.
handle_output ();
break;
// Client is storing a file at
// the server.
case HTTP_Request::PUT:
// ...
}
}
void handle_output (void)
{
// Asynchronously send the file
// to the client.
if (client_->send (file_.data (),
file_.size ())
== SOCKET_ERROR
&& errno == EWOULDBLOCK)
// Register with reactor...
else
// Close down and release resources.
handle_close ();
}
private:
// Set at initialization.
Reactor *reactor_;
// Memory-mapped file_;
Mem_Map file_;
// Socket endpoint.
Socket_Stream *client_;
// HTTP Request holder.
HTTP_Request request_;
};








