DNA.
wyrm-handler
Version.
1.0.9
Language.
c
Manpage.
wyrm_handler (3WY)
Testbase.
Test Script
Test Report
Import.
Interface.
wyrmwif
Package.
wyrmwif
wyrm-io
Export.
Implementation.
wyrm-handler.c
Interface.
wyrm-handler.h
Package.
wyrmhandler.dylib

Tcl Callback Utility

Sections.
Package Files
Handler
Callbacks
Package Interface
Make.
Package.
compile -cc -ld -o [
  export package
] [
  export implementation
] -- -list [import interface] [export interface]
Script.
rule wyrm-handler.package $so/wyrmhandler[info sharedlibextension]
rule wyrm-handler.test [list \
    pkgIndex.tcl \
    $test/wyrm-handler.test.html \
]

rule clean :: {} "
  -rm $test/wyrm-handler.TESTING
"
rule clobber :: {} "
  -rm $include/wyrm-handler.h
  -rm $so/wyrmhandler[info sharedlibextension]
"
   
top

1 :: A utility for other C language packages to simplify callbacks.

Copyright (C) 2004 SM Ryan

Wyrmwif Tcl extensions. For non-profit uses only, provided this copyright is preserved on all copies, this work may be freely copied, modified, redistributed, compiled, and incorporated in other works. This work is distributed with no warranty of any kind; no author or distributor accepts any responsibility for the consequences of using it, or for whether it serves any particular purpose or works at all, unless he or she says so in writing.

   
   

Package Files

   
top
 
section    top
		
#ifndef WYRM_HANDLER_H
#define WYRM_HANDLER_H

	//	wyrm-handler.dna - Copyright (C) 2004 SM Ryan.  All rights reserved.

	#include "wyrmwif.h"
	
	<Exported declarations>

	int Wyrmhandler_Init(Intr intr);
	int Wyrmhandler_SafeInit(Intr intr);

#endif

		
 
section    top
   
   

Handler

   
top

5. Create a handler :: A callback in Tcl usually involves juggling three different values, the callback routine, the argument list to the callback, and the interpreter to evaluate the callback in. wyrm_handler combine these three into one object, and provide operators to add more arguments and to execute the callback. Handlers cope with a dead interpreter inside a script automatically.

Using these routines is a three step process. First, the actual handler code must be written as a function; the function type is the same as Tcl_CreateObjCommand command function. Second, a reference to the handler code and additional parameters is encoded into a ClientData handler value. Third, the Tcl handler is created with the handler ClientData and handler callback envelope.

When Tcl calls back the handler, it will call the envelope with the handler ClientData. The envelope will extract the actual handler and other information from this handler value. When the actual handler returns, error messages, if any, are displayed as background errors. The current results of the interpretter are protected during the actual handler call.

If the interpretter for the handler is deleted, a delete proc can be specified which is able to delete any of its own clientData data structures and elsewise clean up.

A handler can be cancelled, and it again calls the deleter to delete any of its own clientData data structures and elsewise clean up.


typedef struct {
	Tcl_ObjCmdProc *actualHandler; Intr intr; ClientData actualClientData; rWyrmDeleter deleter;
	Obj argv,oargv;
	Intr intrInUse;
	int active; bool deleted,freeing,cancelled;
} Handler;

ClientData wyrm_handler(Intr intr,Tcl_ObjCmdProc *actualHandler,ClientData actualClientData,rWyrmDeleter deleter,int objc,Obj *objv) {
	Handler *handler = heap(Handler);
	handler->actualHandler = actualHandler;
	handler->intr = intr; Tcl_Preserve(intr); handler->intrInUse = 0;
	handler->actualClientData = actualClientData;
	handler->deleter = deleter;
	handler->argv = incr(Tcl_NewListObj(objc,objv)); handler->oargv = 0;
	handler->active = 0;
	handler->deleted = false;
	handler->freeing = false;
	handler->cancelled = false;
	return (ClientData)handler;
}

		
 
section    top
typedef void (*rWyrmDeleter)(Intr,ClientData);
ClientData wyrm_handler(Intr intr,Tcl_ObjCmdProc *actualHandler,ClientData actualClientData,rWyrmDeleter deleter,int objc,Obj *objv);
		
 
section    top

7. Add a Tcl object argument to a handler :: The actual handler has two different ways of receiving arguments. The first is its own ClientData, which can be any representable type that can fit in the ClientData. The ClientData is specified when the handler value is built and passed through unalterred with the actual handler is executed. The actual handler is responsible for managing the storage of the ClientData.

The second way is a list of Tcl objects. A list of objects can be specified when the handler value is created. Subsequently more objects can be added to the handler value with wyrm_handlerArg. All of these objects are made available when the actual handler is called through the objc and objv parameters. This argument list will be reclaimed automatically when the handler value is freed.

Each object in the argument list has its reference count incremented; the reference count is decremented when the handler value is freed.


void wyrm_handlerArg(ClientData handlerValue,Obj arg) {
	Handler *handler = handlerValue;
	Tcl_ListObjAppendElement(0,handler->argv,arg);
}

		
 
section    top
void wyrm_handlerArg(ClientData handlerValue,Obj arg);
		
 
section    top

9. Freeing and cancelling a handler :: A handler value is freed by calling wyrm_handlerFree. It is the user's responsibility to ensure that once freed the Tcl callback is deleted or cancelled, whether the user wished the handler to be freed or not. A handler can be freed in three ways. (1) Tcl called back the handler but the interpretter has been deleted; the actual handler is not called. (2) The handler value is freed from within the actual handler, by calling wyrm_handlerFreeSelf or wyrm_handlerFree with the correct handler value. (3) The handler value is freed outside the callback mechanism; this is called a cancel.

When the handler value is created, along with actual handler function a deleter function may also be specifed. If this is non-NULL it is called when the handler value is freed. This function can take responsibility for deleting actual client data allocated memory as well as cancelling or deleting the callback. When the deleter is called, wyrm_handlerCancelled returns true if the handler has been cancelled (case (3) above); otherwise it returns false (case (1) and (2) above).

For example, a timer handler is only called once. In cases (1) and (2) it has already been triggerred and deleted by Tcl. But in case (3) the timer handler needs to be deleted when the handler value is freed.


#define getActiveHandler(intr) ((intr)?(Handler*)(Tcl_GetAssocData(intr,"wyrm.handler.active",0)):0)
#define putActiveHandler(intr,h) (Tcl_SetAssocData(intr,"wyrm.handler.active",0,(ClientData)(h)))

static void freeHandler(Handler *handler) {
	<Push the handler on the stack of active and freeing handlers>
	handler->freeing = true;
	if (handler->deleter) handler->deleter(handler->intr,handler->actualClientData);
	decr(handler->argv);
	<Pop the handler off the stack of active and freeing handlers>
	Tcl_Release(handler->intr);
	if (handler->active) Tcl_Panic("handler freed while still active");
	dispose(handler);
}

void wyrm_handlerFree(ClientData handlerValue) {
	Handler *handler = handlerValue;
	if (handler->freeing) return;
	handler->deleted = true;
	handler->cancelled = !handler->active;
	if (handler->active) return;
	handler->intrInUse = handler->intr;
	freeHandler(handler);
}

void wyrm_handlerFreeSelf(Intr intr) {
	Handler *h = getActiveHandler(intr);
	if (h) wyrm_handlerFree(h);
}

bool wyrm_handlerCancelled(Intr intr) {
	Handler *h = getActiveHandler(intr);
	return h ? h->cancelled : false;
}

		
 
section    top
void wyrm_handlerFree(ClientData handlerValue);
void wyrm_handlerFreeSelf(Intr);
bool wyrm_handlerCancelled(Intr);
		
 
section    top

if (Tcl_InterpDeleted(handler->intr)) {
	wyrm_handlerFree(handler);
	return;
}

		
 
section    top

if (handler->deleted) freeHandler(handler);
else handler->intrInUse = 0;

		
 
section    top

ClientData wyrm_handlerSelf(Intr intr) {
	return getActiveHandler(intr);
}

		
 
section    top
ClientData wyrm_handlerSelf(Intr);
		
 
section    top

Handler *ohandler = getActiveHandler(handler->intrInUse);
putActiveHandler(handler->intrInUse,handler);
handler->active += 1;

		
 
section    top

putActiveHandler(handler->intrInUse,ohandler);
handler->active -= 1;

		
   
   

Callbacks

   
top

void wyrm_handlerCallbackProc(ClientData clientData) {
	Handler *handler = clientData;
	if (handler->freeing) return;
	handler->intrInUse = handler->intr;
	<Forego the call back and free the handler if the interpretter is deleted>
	<Push the handler on the stack of active and freeing handlers>
	Tcl_SavedResult state;
	Tcl_SaveResult(handler->intr,&state);
	Obj errorInfo = incr(Tcl_GetVar2Ex(handler->intr,"errorInfo",0,TCL_GLOBAL_ONLY));
	Obj errorCode = incr(Tcl_GetVar2Ex(handler->intr,"errorCode",0,TCL_GLOBAL_ONLY));
	int N; Obj *P; if (Tcl_ListObjGetElements(0,handler->argv,&N,&P)!=TCL_OK) {N = 0; P = 0;}
	int rc = handler->actualHandler(handler->actualClientData,handler->intr,N,P);
	if (handler->oargv) {decr(handler->argv); handler->argv = handler->oargv; handler->oargv = 0;}
	if (rc!=TCL_OK) Tcl_BackgroundError(handler->intr);
	Tcl_RestoreResult(handler->intr,&state);
	Tcl_SetVar2Ex(handler->intr,"errorInfo",0,errorInfo,TCL_GLOBAL_ONLY); decr(errorInfo);
	Tcl_SetVar2Ex(handler->intr,"errorCode",0,errorCode,TCL_GLOBAL_ONLY); decr(errorCode);
	<Pop the handler off the stack of active and freeing handlers>
	<Free the handler after a call back if handler was deleted>
}
		
 
section    top
void wyrm_handlerCallbackProc(ClientData clientData);
		
 
section    top

void wyrm_handlerChannelProc(ClientData clientData,int mask) {
	Handler *handler = clientData;
	handler->oargv = incr(Tcl_DuplicateObj(handler->argv));
	wyrm_handlerArg(clientData,Tcl_NewIntObj(mask));
	wyrm_handlerCallbackProc(clientData);
}
		
 
section    top
void wyrm_handlerChannelProc(ClientData clientData,int mask);
		
 
section    top

void wyrm_handlerInterpretterProc(ClientData clientData,Intr intr) {
	Handler *handler = clientData;
	if (handler->freeing) return;
	handler->intrInUse = intr;
	<Push the handler on the stack of active and freeing handlers>
	Tcl_SavedResult state;
	Tcl_SaveResult(intr,&state);
	Obj errorInfo = incr(Tcl_GetVar2Ex(intr,"errorInfo",0,TCL_GLOBAL_ONLY));
	Obj errorCode = incr(Tcl_GetVar2Ex(intr,"errorCode",0,TCL_GLOBAL_ONLY));
	int N; Obj *P; if (Tcl_ListObjGetElements(0,handler->argv,&N,&P)!=TCL_OK) {N = 0; P = 0;}
	handler->actualHandler(handler->actualClientData,intr,N,P);
	if (handler->oargv) {decr(handler->argv); handler->argv = handler->oargv; handler->oargv = 0;}
	Tcl_RestoreResult(intr,&state);
	Tcl_SetVar2Ex(intr,"errorInfo",0,errorInfo,TCL_GLOBAL_ONLY); decr(errorInfo);
	Tcl_SetVar2Ex(intr,"errorCode",0,errorCode,TCL_GLOBAL_ONLY); decr(errorCode);
	<Pop the handler off the stack of active and freeing handlers>
	<Free the handler after a call back if handler was deleted>
}
		
 
section    top
void wyrm_handlerInterpretterProc(ClientData clientData,Intr intr);
		
 
section    top

int wyrm_handlerAsyncCallbackProc(ClientData clientData,Intr intr,int code) {
	Handler *handler = clientData;
	if (handler->freeing) return code;
	handler->intrInUse = intr;
	<Push the handler on the stack of active and freeing handlers>
	Tcl_SavedResult state;
	Tcl_SaveResult(intr,&state);
	Obj errorInfo = incr(Tcl_GetVar2Ex(intr,"errorInfo",0,TCL_GLOBAL_ONLY));
	Obj errorCode = incr(Tcl_GetVar2Ex(intr,"errorCode",0,TCL_GLOBAL_ONLY));
	int N; Obj *P; if (Tcl_ListObjGetElements(0,handler->argv,&N,&P)!=TCL_OK) {N = 0; P = 0;}
	int rc = handler->actualHandler(handler->actualClientData,intr,N,P);
	if (handler->oargv) {decr(handler->argv); handler->argv = handler->oargv; handler->oargv = 0;}
	if (rc==TCL_OK) {
		Tcl_RestoreResult(intr,&state);
		Tcl_SetVar2Ex(intr,"errorInfo",0,errorInfo,TCL_GLOBAL_ONLY); decr(errorInfo);
		Tcl_SetVar2Ex(intr,"errorCode",0,errorCode,TCL_GLOBAL_ONLY); decr(errorCode);
	}else {
		code = rc;
		Tcl_DiscardResult(&state);
		decr(errorInfo);
		decr(errorCode);
	}
	<Pop the handler off the stack of active and freeing handlers>
	<Free the handler after a call back if handler was deleted>
	return code;
}
		
 
section    top
int wyrm_handlerAsyncCallbackProc(ClientData clientData,Intr intr,int code);
		
 
section    top

25. Cancelling callbacks :: When a handler is freed, it necessary to cancel the call back as the handler ClientData is no longer valid; and when a callback is cancelled, it is necessary to free the the handler so it does not become garbage. This package does not have enough information to link these together. One strategy is to define a deletion function and let it cancel callbacks if necessary. For example,

static int channelHandler(ClientData clientData,Intr intr,int N,Tcl_Obj *CONST P[]) {
Tcl_Channel channel = clientData;
if (Tcl_Eof(channel)) {
wyrm_handlerFreeSelf(intr);
}else {
...
}
}
static void channelCloser(Intr intr,ClientData clientData) {
Tcl_Channel channel = clientData;
if (wyrm_handlerCancelled(intr)) {
Tcl_DeleteChannelHandler(channel, wyrm_handlerChannelProc, wyrm_handlerSelf(intr));
}
Tcl_UnregisterChannel(intr,channel);
}
...
Tcl_Channel channel = ...;
Tcl_CreateChannelHandler(channel,wyrm_handlerChannelProc,
wyrm_handler(intr,channelHandler,channel,channelCloser,0,0));

   
   

Package Interface

   
top

#ifdef TESTING
	#include <string.h>
	
	static Obj logList = 0;
	
	static void reset(void) {decr(logList); logList = 0;}
	static Obj log(Obj message) {
		if (!logList)  logList = incr(Tcl_NewStringObj("LOG",-1));
		Tcl_AppendToObj(logList,"\n",1);
		Tcl_AppendObjToObj(logList,message);
		return message;
	}
	static Obj show(void) {
		if (!logList)  logList = incr(Tcl_NewStringObj("LOG",-1));
		return logList;
	}
	
	typedef struct {Obj script; int type; Tcl_TimerToken token; Tcl_Channel channel;} Callback;

	static int qhandlercallback(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
		Callback *callback = clientData;
		if (callback->type==2 || callback->type==3) {
			char buffer[4096];
			while (cread(callback->channel,buffer,sizeof buffer)>0) ;
		}
		decr(log(oprintf("callback script=SC<%x> type=%d intr=IN<%x> %{y-}s",
				callback->script,callback->type,intr,incr(Tcl_NewListObj(N,P)))));
		Tcl_SetVar(intr,"CALLBACK","1",TCL_GLOBAL_ONLY);
		int rc = Tcl_EvalObjEx(intr,callback->script,0);
		if (callback->type==3 && Tcl_Eof(callback->channel)) {
			Tcl_UnregisterChannel(intr,callback->channel); return TCL_OK;
		}
		return rc;
	}

	static void qhandlerdelete(Intr intr,ClientData clientData) {
		Callback *callback = clientData;
		int deleted = Tcl_InterpDeleted(intr);
		decr(log(oprintf("delete script=SC<%x> type=%d intr=IN<%x> del=%d can=%d",
				callback->script,callback->type,intr,deleted,wyrm_handlerCancelled(intr))));
		if (!deleted) {
			Tcl_SetVar(intr,"CALLBACK","0",TCL_GLOBAL_ONLY);
			if (Tcl_EvalObjEx(intr,callback->script,0)!=TCL_OK) decr(log(oprintf("script failed")));
		}
		switch (callback->type) {
			case 1:
				if (wyrm_handlerCancelled(intr)) {
					Tcl_DeleteTimerHandler(callback->token);
				}
				break;
			case 2:
				Tcl_DeleteCloseHandler(callback->channel,wyrm_handlerCallbackProc,wyrm_handlerSelf(intr));
				break;
			case 3:
				Tcl_DeleteChannelHandler(callback->channel,wyrm_handlerChannelProc,wyrm_handlerSelf(intr));
				break;
		}
		decr(callback->script); dispose(callback);
	}

	static int qhandlercommand(ClientData clientData,Intr intr,int N,Tcl_Obj *const P[]) {
		int index;
		static chars subcommand[] = {
			"newhandler","self",
			"commandinterpretter",
			"freehandler","freeself","cancelled",
			"argument",
			"log","reset","show",
			"timer","closer","channel",
			"create","eval","delete",
			0
		};
		enum {
			o_newhandler,o_self,
			o_commandinterpretter,
			o_freehandler,o_freeself,o_cancelled,
			o_argument,
			o_log,o_reset,o_show,
			o_timer,o_closer,o_channel,
			o_create,o_eval,o_delete
		};

		if (Tcl_GetIndexFromObj(intr,P[1],(CONST char**)subcommand,"subcommand",0,&index)!=TCL_OK) return TCL_ERROR;
		switch (index) {
			case o_newhandler: {
				/*  qhandler newhandler script ... ->   CB<x> SC<x> IN<x>   */
				Callback *callback = heap(Callback);
				callback->script = incr(P[2]); callback->type = 0;
				ClientData handler = wyrm_handler(intr,qhandlercallback,callback,qhandlerdelete,N-3,P+3);
				rprintf(intr,"CB<%x> SC<%x> IN<%x>",handler,P[2],intr);
				return TCL_OK;
			}
			case o_self: {
				/*  qhandler self IN<x> ->   CB<x>   */
				Intr x = (Intr)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				rprintf(intr,"CB<%x>",wyrm_handlerSelf(x));
				return TCL_OK;
			}
			case o_commandinterpretter: {
				/*  qhandler commandinterpretter ->   IN<x>   */
				rprintf(intr,"IN<%x>",intr);
				return TCL_OK;
			}
			case o_freehandler: {
				/*  qhandler freehandler CB<x> ->     */
				ClientData handler = (ClientData)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				wyrm_handlerFree(handler);
				return TCL_OK;
			}
			case o_freeself: {
				/*  qhandler freeself IN<x> ->   */
				Intr x = (Intr)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				wyrm_handlerFreeSelf(x);
				return TCL_OK;
			}
			case o_cancelled: {
				/*  qhandler cancelled IN<x> ->   */
				Intr x = (Intr)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				Tcl_SetObjResult(intr,Tcl_NewBooleanObj(wyrm_handlerCancelled(x)));
				return TCL_OK;
			}
			case o_argument: {
				/*  qhandler argument CB<x> ... ->   */
				ClientData handler = (ClientData)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				for (N-=3,P+=3; N>0; N--,P++) wyrm_handlerArg(handler,*P);
				return TCL_OK;
			}
			case o_log: {
				/*  qhandler log message ->   */
				log(P[2]);
				return TCL_OK;
			}
			case o_reset: {
				/*  qhandler reset ->   */
				reset();
				return TCL_OK;
			}
			case o_show: {
				/*  qhandler show  ->   messages   */
				Tcl_SetObjResult(intr,show());
				return TCL_OK;
			}
			case o_timer: {
				/*  qhandler timer CB<x> msecs  ->   */
				ClientData handler = (ClientData)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				Handler *h = handler;
				Callback *c = h->actualClientData;
				long msecs; Tcl_GetLongFromObj(0,P[3],&msecs);
				c->type = 1; c->token = Tcl_CreateTimerHandler(msecs,wyrm_handlerCallbackProc,handler);
				return TCL_OK;
			}
			case o_closer: {
				/*  qhandler closer CB<x> channel  ->   */
				ClientData handler = (ClientData)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				Handler *h = handler;
				Callback *c = h->actualClientData;
				int mood; Tcl_Channel channel = Tcl_GetChannel(intr,Tcl_GetString(P[3]),&mood);
				c->type = 2; c->channel = channel;
				Tcl_CreateCloseHandler(channel,wyrm_handlerCallbackProc,handler);
				return TCL_OK;
			}
			case o_channel: {
				/*  qhandler channel CB<x> channel  ->   */
				ClientData handler = (ClientData)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				Handler *h = handler;
				Callback *c = h->actualClientData;
				int mood; Tcl_Channel channel = Tcl_GetChannel(intr,Tcl_GetString(P[3]),&mood);
				c->type = 3; c->channel = channel;
				Tcl_CreateChannelHandler(channel,mood|TCL_EXCEPTION,wyrm_handlerChannelProc,handler);
				return TCL_OK;
			}
			case o_create: {
				char initialisation [] =
					"source $tcl_library/init.tcl\n"
					"package require msgcat\n"
					"catch {\n"
						"package require wyrmwif\n"
						"package require wyrmhandler\n"
						"namespace import wyrm::*\n"
					"}\n"
				;
				Intr alt = Tcl_CreateInterp();
				#ifdef TCL_MEM_DEBUG
					Tcl_InitMemory(alt);
				#endif
				if (Tcl_Init(alt) == TCL_ERROR) {
					fprintf(stderr,"interpretter Tcl_Init failed: %s\n",
							Tcl_GetStringResult(alt));
				}
				Tcl_SetVar(alt,"tcl_rcFileName","~/.tclshrc",TCL_GLOBAL_ONLY);
				Tcl_SourceRCFile(alt);
				if (Tcl_Eval(alt,initialisation)!=TCL_OK) {
					fprintf(stderr,"interpretter initialisation failed: %s\n%s",
							Tcl_GetStringResult(alt),initialisation);
				}
				rprintf(intr,"IN<%x>",alt);
			}	return TCL_OK;
			case o_eval: {
				Intr alt = (Intr)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				int rc = Tcl_EvalObjEx(alt,P[3],0);
				Tcl_SetObjResult(intr,Tcl_GetObjResult(alt));
				return rc;
			}
			case o_delete: {
				Intr alt = (Intr)(strtol(strchr(Tcl_GetString(P[2]),'<')+1,0,16));
				Tcl_DeleteInterp(alt);
				Tcl_ResetResult(intr);
				return TCL_OK;
			}
			
		}
		Tcl_Panic("you can't get here from there");
	}
#endif

int Wyrmhandler_Init(Intr intr) {
	Tcl_PkgRequire(intr,"wyrmwif","1",0);
	Tcl_PkgProvide(intr,"wyrmhandler",VERSION);
	#ifdef TESTING
		Tcl_CreateObjCommand(intr,"::wyrm::qhandler",qhandlercommand,0,0);
		return Tcl_VarEval(intr,"namespace eval ::wyrm {namespace export qhandler}",0);
	#else
		return TCL_OK;
	#endif
}

int Wyrmhandler_SafeInit(Intr intr) {
	return rprintf(intr,"This package only provides a C API; loading it has no benefit.",TCL_ERROR);
}

		
 
section    top
    HDR Callback handlers.
      HDR000
      Callback scripts.
        Timer callback.
          HDR010
        Channel close callback.
          HDR011
        Channel event callback.
          HDR012
      Interpretter.
        Callback scripts.
          Timer callback in auxillary interpretter.
            HDR100
          Channel close callback in auxillary interpretter.
            HDR101
          Channel event callback in auxillary interpretter.
            HDR102
        Interpretter deleted.
          Interpretter deleted callback in auxillary interpretter.
            HDR150
      Handler.
        Handler freed from the callback.
          Timer callback.
            HDR200
          Channel close callback.
            HDR201
          Channel event callback.
            HDR202
        Handler freed from the deleter.
          Timer callback.
            HDR250
          Channel close callback.
            HDR251
          Channel event callback.
            HDR252
        Handler cancelled.
          Timer callback.
            HDR280
          Channel close callback.
            HDR281
          Channel event callback.
            HDR282
        Handler arguments added.
          Timer callback.
            HDR300
          Channel close callback.
            HDR301
          Channel event callback.
            HDR302