File: _kievents.c

package info (click to toggle)
python-kinterbasdb 3.1-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 1,044 kB
  • ctags: 1,157
  • sloc: ansic: 6,879; python: 2,517; makefile: 77
file content (763 lines) | stat: -rw-r--r-- 23,957 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
/* KInterbasDB Python Package - Implementation of Events Support
**
** Version 3.1
**
** The following contributors hold Copyright (C) over their respective
** portions of code (see license.txt for details):
**
** [Original Author (maintained through version 2.0-0.3.1):]
**   1998-2001 [alex]  Alexander Kuznetsov   <alexan@users.sourceforge.net>
** [Maintainers (after version 2.0-0.3.1):]
**   2001-2002 [maz]   Marek Isalski         <kinterbasdb@maz.nu>
**   2002-2004 [dsr]   David Rushby          <woodsplitter@rocketmail.com>
** [Contributors:]
**   2001      [eac]   Evgeny A. Cherkashin  <eugeneai@icc.ru>
**   2001-2002 [janez] Janez Jere            <janez.jere@void.si>
*/

#include "_kievents.h"

#ifdef ENABLE_DB_EVENT_SUPPORT

#include "_kinterbasdb_exceptions.h"

#include "_kievents_platform_defs.h"


#define WAIT_INFINITELY 0.0

/****************** "PRIVATE" DECLARATIONS:BEGIN *******************/
/* Note this file's "public" functions are declared in _kievents.h. */

static EventConduitObject *_event_conduit_new(
    PyObject *event_names, ConnectionObject *conn
  );

void pyob_event_conduit_del(PyObject *conduit);
static void _event_conduit_delete(EventConduitObject *conduit);

static void _event_queue_delete(EventQueue *queue);

static int _event_conduit_cancel(EventConduitObject *conduit);

static long _event_queue_flush(EventQueue *queue);

static int _event_conduit_allocate_event_count_buffers (
    EventConduitObject *conduit, PyObject *event_names
  );

static PyObject *_construct_event_count_dict(
    PyObject *py_event_names, long *event_occurrence_counts
  );

/* 2003.03.04: */
static isc_callback _event_callback( EventConduitObject *conduit,
    short updated_buffer_length, char *updated_buffer
  );

static int _event_conduit_enqueue_handler( EventConduitObject *conduit,
    boolean allowed_to_raise_exception
  );

/****************** "PRIVATE" DECLARATIONS:END *******************/


/****************** TYPE DEFINITIONS:BEGIN *******************/
PyTypeObject EventConduitType = {
  PyObject_HEAD_INIT( NULL )

  0,
  "kinterbasdb.EventConduit",
  sizeof( EventConduitObject ),
  0,
  pyob_event_conduit_del,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0
};
/****************** TYPE DEFINITIONS:END *******************/


/**************** PLATFORM-SPECIFIC EVENT FUNCTIONS:BEGIN *****************/
PyObject *abstract_wait(EventConduitObject *conduit, long timeout_millis);

/* Import the platform-specific event handling implementation. */
#ifdef MS_WIN32
  #include "_kievents_wait_windows.c"
#else
  /* All non-Win32 OSes are assumed to be UNIX-ish. */
  #include "_kievents_wait_unix.c"
#endif

/**************** PLATFORM-SPECIFIC EVENT FUNCTIONS:END *****************/


/********************** COMMON EVENT FUNCTIONS:BEGIN ***********************/

PyObject *pyob_event_conduit_new( PyObject *self, PyObject *args ) {
  EventConduitObject *conduit;
  PyObject *event_names;
  ConnectionObject *conn;

  /* Begin parameter validation section. */
  if ( !PyArg_ParseTuple(args, "OO!", &event_names, &ConnectionType, &conn) ) {
    return NULL;
  }
  /* event_names has already been validated by the surrounding Python code. */

  conduit = _event_conduit_new(event_names, conn);
  if (conduit == NULL) {
    goto PYOB_EVENTS_REGISTER_FAIL;
  }

  /* The event conduit object has now been created, but it will not be
  ** "hooked into the event system" until its wait() method is called. */
  return (PyObject *) conduit;

PYOB_EVENTS_REGISTER_FAIL:
  Py_XDECREF(conn);

  return NULL;
} /* pyob_event_conduit_new */


PyObject *pyob_event_conduit_wait( PyObject *self, PyObject *args ) {
  EventConduitObject *conduit;
  PyObject *wait_result;
  /* 2003.03.05: */
  float timeout_secs = WAIT_INFINITELY;
  long timeout_millis = (long) WAIT_INFINITELY;

  if ( !PyArg_ParseTuple(args, "O!f", &EventConduitType, &conduit, &timeout_secs) ) {
    return NULL;
  }

  if (timeout_secs < 0.0) {
    raise_exception(ProgrammingError, "Negative timeout is not valid.");
    return NULL;
  }

  if (timeout_secs != WAIT_INFINITELY) {
    timeout_millis = (long) (timeout_secs * 1000);
  }

  wait_result = abstract_wait(conduit, timeout_millis);

  return wait_result;
} /* pyob_event_conduit_wait */


PyObject *pyob_event_conduit_flush_queue( PyObject *self, PyObject *args ) {
  /* Returns an integer containing the number of event notification objects
  ** that were flushed from the queue. */
  EventConduitObject *conduit;
  EventQueue *queue;
  PyObject *nodes_flushed_count;

  if ( !PyArg_ParseTuple(args, "O!", &EventConduitType, &conduit) ) {
    return NULL;
  }

  /* In the future, each queue will probably have its own thread lock, but
  ** that's not necessary right now because the DB thread lock serializes
  ** access to queues. */
  ENTER_DB_WITHOUT_LEAVING_PYTHON

  queue = conduit->queue;
  if (queue == NULL) {
    nodes_flushed_count = PyInt_FromLong(0);
  } else {
    int flush_result = _event_queue_flush(queue);
    if (flush_result < 0) {
      LEAVE_DB_WITHOUT_ENTERING_PYTHON /* 2004.04.30: Lock wasn't released on error. */
      return NULL;
    }
    nodes_flushed_count = PyInt_FromLong(flush_result);
  }

  LEAVE_DB_WITHOUT_ENTERING_PYTHON

  return nodes_flushed_count;
} /* pyob_event_conduit_flush_queue */


PyObject *pyob_event_conduit_cancel( PyObject *self, PyObject *args ) {
  EventConduitObject *conduit;

  if ( !PyArg_ParseTuple(args, "O!", &EventConduitType, &conduit) ) {
    return NULL;
  }

  if (_event_conduit_cancel(conduit) != 0) {
    return NULL;
  }

  RETURN_PY_NONE;
} /* pyob_event_conduit_cancel */

/********************** COMMON EVENT FUNCTIONS:BEGIN ***********************/


/******************** UTILITY FUNCTIONS:BEGIN ********************/

static EventConduitObject *_event_conduit_new(PyObject *event_names, ConnectionObject *conn) {
  EventConduitObject *conduit;

  conduit = PyObject_New( EventConduitObject, &EventConduitType );
  if (conduit == NULL) {
    return (EventConduitObject *) PyErr_NoMemory();
  }

 /* Create the event queue associated with this conduit. */
  conduit->queue = kimem_main_malloc(sizeof(EventQueue));
  if (conduit->queue == NULL) {
    PyErr_NoMemory();
    goto EVENT_CONDUIT_NEW_ERROR;
  }

  conduit->queue->event = platform_create_event_object();
  if (conduit->queue->event == NULL) {
    raise_exception(OperationalError, "Unable to create native event object.");
    goto EVENT_CONDUIT_NEW_ERROR;
  }

  /* Set the head of the linked list to NULL to indicate that the list is empty. */
  conduit->queue->head = NULL;
 /* Done creating event queue. */

  /* The _event_conduit_allocate_event_count_buffers function call allocates
  ** conduit->event_buffer, conduit->result_buffer, and conduit->buffer_length: */
  if ( _event_conduit_allocate_event_count_buffers(conduit, event_names) != 0 ) {
    goto EVENT_CONDUIT_NEW_ERROR;
  }

  conduit->event_id = kimem_main_malloc(sizeof(ISC_LONG *));
  if (conduit->event_id == NULL) {
    PyErr_NoMemory();
    goto EVENT_CONDUIT_NEW_ERROR;
  }
  *(conduit->event_id) = -1;

  conduit->status = none;

  Py_INCREF(event_names);
  conduit->py_event_names = event_names;

  Py_INCREF(conn);
  conduit->connection = conn;

  return conduit;

EVENT_CONDUIT_NEW_ERROR:
  pyob_event_conduit_del( (PyObject *) conduit );
  return NULL;
} /* _event_conduit_new */


void pyob_event_conduit_del(PyObject *conduit) {
  EventConduitObject *conduit_cast = (EventConduitObject *) conduit;

  _event_conduit_cancel(conduit_cast);

  /* Free the memory held by the member pointers of the structure, then free
  ** the structure itself. */
  _event_conduit_delete(conduit_cast);
  PyObject_Del(conduit);
} /* pyob_event_conduit_del */


static void _event_conduit_delete(EventConduitObject *conduit) {
  _event_queue_delete(conduit->queue);

  /* The event buffer and the result buffer are allocated by the DB client
  ** library (in the isc_event_block function, specifically).  They *must* be
  ** freed by the database client library's own memory handler. */
  ENTER_DB
  if (conduit->event_buffer != NULL) {
    kimem_db_client_free(conduit->event_buffer);
  }
  if (conduit->result_buffer != NULL) {
    kimem_db_client_free(conduit->result_buffer);
  }
  LEAVE_DB

  /* Note that in a typical garbage collection of an EventConduit object,
  ** _event_conduit_cancel is called by pyob_event_conduit_del before this
  ** function is called.  This function should concern itself *solely* with
  ** memory deallocation, not with the cancellation of event registrations
  ** or other high-level issues. */
  if (conduit->event_id != NULL) {
    kimem_main_free(conduit->event_id);
    conduit->event_id = NULL;
  }

  Py_XDECREF(conduit->py_event_names);
  Py_XDECREF(conduit->connection);
} /* _event_conduit_delete */


static void _event_queue_delete(EventQueue *queue) {
  if (queue == NULL) {
    return;
  }

  /* Free any event notifications objects in the queue (this must be done
  ** before the queue's event object is destroyed, because _event_queue_flush
  ** uses that event object). */
  _event_queue_flush(queue);

  /* Free the queue's event object. */
  platform_free_event_object(queue->event);
  queue->event = NULL;
} /* _event_conduit_delete */


static long _event_queue_flush(EventQueue *queue) {
  /* Walk through the queue (a linked list) and delete all of its nodes. */
  EventQueueItem *iter_free, *iter_next;
  long nodes_freed_count = 0;

  iter_free = queue->head;
  while (iter_free != NULL) {
    nodes_freed_count++;

    iter_next = iter_free->next;
    /* Plain free, rather than kimem_main_free, is appropriate here because
    ** we don't hold the GIL. */
    kimem_plain_free(iter_free);
    iter_free = iter_next;
  }
  queue->head = NULL;

  /* Clear the queue's event object so that the next wait() call will work
  ** properly. */
  {
    int unsignal_result = event_queue_unsignal(queue);
    if (unsignal_result < 0) {
      raise_exception(OperationalError, "Could not clear native event object.");
      return unsignal_result;
    }
  }

  return nodes_freed_count;
} /* _event_queue_flush */


static int _event_conduit_cancel(EventConduitObject *conduit) {
  ConnectionObject *conn = conduit->connection;

  /* If there is no event id or its value is set to an "inactive" flag, we need
  ** not do anything. */
  if (conduit->event_id != NULL && *(conduit->event_id) != -1) {
    ISC_LONG *local_event_id;

    /* 2003.04.02: */
    /* Set conduit->event_id to NULL in order to indicate to the event callback
    ** thread that the conduit is no longer viable.  Retain a local pointer to
    ** the event id's storage so that it can be freed after the call to
    ** isc_cancel_events below. */
    ENTER_DB
    local_event_id = conduit->event_id;
    conduit->event_id = NULL;
    LEAVE_DB

    /* 2003.04.02: */
    /* The DB lock must not be held when we call isc_cancel_events, because
    ** isc_cancel_events causes the event handler thread to do a dummy run of
    ** the event callback function.  The entire event callback function is
    ** synchronized on the DB lock, so the event handler thread deadlocks
    ** instead of shutting down if the thread that called isc_cancel_events is
    ** still holding the DB lock. */
    isc_cancel_events(conn->status_vector, &(conn->db_handle), local_event_id);

    /* conduit->event_id should already have been nulled at the beginning of
    ** this function; instead, the local variable local_event_id now points to
    ** the storage, which must be freed now. */
    assert(conduit->event_id == NULL);
    kimem_main_free(local_event_id);

    if ( DB_API_ERROR(conn->status_vector) ) {
      raise_sql_exception( OperationalError,
          "Could not cancel event registration: ", conn->status_vector
        );
      return -1;
    }
  }

  return 0;
} /* _event_conduit_cancel */


static int _event_conduit_allocate_event_count_buffers (
    EventConduitObject *conduit, PyObject *event_names
  )
{
  /* Build event count buffer using isc_event_block, for up to
  ** MAX_EVENT_NAMES events.  I know of no way to gracefully and portably
  ** "apply(C_function, Py_Sequence)", so this is really ugly. */
  int i;
  short event_names_count = (short) PySequence_Length(event_names);
  PyObject *en[MAX_EVENT_NAMES];

  for (i = 0; i < event_names_count; i++) {
    /* PySequence_GetItem creates new references; they are released in a loop
    ** below. */
    en[i] = PySequence_GetItem(event_names, i);
  }

  ENTER_DB_WITHOUT_LEAVING_PYTHON

  #define ISC_EVENT_BLOCK_BEGIN conduit->buffer_length = \
    (short)isc_event_block( \
      &(conduit->event_buffer), &(conduit->result_buffer), event_names_count,
  #define ISC_EVENT_BLOCK_END ); break;
  #define EN_STR(i) PyString_AsString(en[i])

  switch (event_names_count) {
    case 1:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0)
      ISC_EVENT_BLOCK_END
    case 2:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1)
      ISC_EVENT_BLOCK_END
    case 3:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2)
      ISC_EVENT_BLOCK_END
    case 4:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3)
      ISC_EVENT_BLOCK_END
    case 5:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4)
      ISC_EVENT_BLOCK_END
    case 6:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5)
      ISC_EVENT_BLOCK_END
    case 7:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6)
      ISC_EVENT_BLOCK_END
    case 8:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7)
      ISC_EVENT_BLOCK_END
    case 9:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7), EN_STR(8)
      ISC_EVENT_BLOCK_END
    case 10:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7), EN_STR(8), EN_STR(9)
      ISC_EVENT_BLOCK_END
    case 11:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7), EN_STR(8), EN_STR(9),
        EN_STR(10)
      ISC_EVENT_BLOCK_END
    case 12:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7), EN_STR(8), EN_STR(9),
        EN_STR(10), EN_STR(11)
      ISC_EVENT_BLOCK_END
    case 13:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7), EN_STR(8), EN_STR(9),
        EN_STR(10), EN_STR(11), EN_STR(12)
      ISC_EVENT_BLOCK_END
    case 14:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7), EN_STR(8), EN_STR(9),
        EN_STR(10), EN_STR(11), EN_STR(12), EN_STR(13)
      ISC_EVENT_BLOCK_END
    case 15:
      ISC_EVENT_BLOCK_BEGIN
        EN_STR(0), EN_STR(1), EN_STR(2), EN_STR(3), EN_STR(4),
        EN_STR(5), EN_STR(6), EN_STR(7), EN_STR(8), EN_STR(9),
        EN_STR(10), EN_STR(11), EN_STR(12), EN_STR(13), EN_STR(14)
      ISC_EVENT_BLOCK_END

    /* No default case is necessary because the length of event_names was
    ** already validated. */
  }

  LEAVE_DB_WITHOUT_ENTERING_PYTHON

  /* PySequence_GetItem returned a new reference; we must release it. */
  for (i = 0; i < event_names_count; i++) {
    Py_DECREF(en[i]);
  }

  if (conduit->buffer_length <= 0) {
    PyErr_NoMemory();
    return -1;
  }

  return 0;
}


static PyObject *_construct_event_count_dict(
    PyObject *py_event_names, long *event_occurrence_counts
  )
{
  /* Construct a dictionary that maps
  **   (event name -> number of times that specific event occurred) */
  PyObject *countDict;
  short event_names_count;
  short i;

  event_names_count = PySequence_Length(py_event_names);

  countDict = PyDict_New();
  if (countDict == NULL) {
    return PyErr_NoMemory();
  }

  for (i = 0; i < event_names_count; i++) {
    /* Both PySequence_GetItem and PyInt_FromLong create new references; we
    ** must DECREF them after passing them to PyDict_SetItem, which creates
    ** its new references of its own. */
    PyObject *key = PySequence_GetItem(py_event_names, i);
    PyObject *value = PyInt_FromLong(event_occurrence_counts[i]);

    PyDict_SetItem(countDict, key, value);

    Py_DECREF(key);
    Py_DECREF(value);
  }

  return countDict;
} /* _construct_event_count_dict */


/******************** UTILITY FUNCTIONS:END ********************/


/* 2003.03.04: Moved the following functions into the generic code from
** _kievents_wait_windows.c:  */
isc_callback _event_callback( EventConduitObject *conduit,
    short updated_buffer_length, char *updated_buffer
  )
{
/* Notice that this callback NEVER manipulates the Python GIL in any way, so
** it is not safe to call the Python API within it.  However, the entire
** callback is protected by the DB thread lock. */
ENTER_DB_WITHOUT_LEAVING_PYTHON
{ /* begin block that contains the actual callback functionality */

  /* A vector into which event counts will be stored by isc_event_counts: */
  ISC_ULONG event_count_vector[STATUS_VECTOR_SIZE];

  boolean is_dummy_run;
  int signal_result = 0;

  /* When event registration is cancelled via isc_cancel_events, the handler
  ** thread (this thread) is called so that it can clean up after itself.
  ** This event handler doesn't need to clean up after itself; it can just
  ** return (after having released the database lock, of course).  Since this
  ** thread does not requeue itself in this case, it will "die". */
  if (conduit == NULL || conduit->event_id == NULL)
  {
    goto EVENT_CALLBACK_EXIT;
  }

  is_dummy_run = (conduit->status == after_initial_enqueue);

  /* Update the event count buffer to record the fact that this occurrence
  ** (or pseudo-occurrence (i.e., the initial 'dummy run')) of the event was
  ** handled. */
  memcpy(conduit->result_buffer, updated_buffer, updated_buffer_length);

  /* DB threadlock is already held, so no problem in calling isc_* function. */
  isc_event_counts(
      event_count_vector,
      conduit->buffer_length,
      conduit->event_buffer,
      conduit->result_buffer
    );

  if (!is_dummy_run) {
    /* This run is the real thing; insert an object into the event queue. */
    int i;
    EventQueue *queue = conduit->queue;

    /* Create new queue item. */
    /* Since we don't hold the Python GIL, we must use system malloc rather
    ** than kimem_main_malloc.  The thread that retrieves the queue item will
    ** be responsible for freeing it (with kimem_plain_free). */
    EventQueueItem *new_item = kimem_plain_malloc(sizeof(EventQueueItem));
    new_item->next = NULL;

    for (i = 0; i < MAX_EVENT_NAMES; i++) {
      new_item->counts[i] = (long) event_count_vector[i];
    }

    /* Insert the new item into the queue. */
    if (queue->head == NULL) {
      queue->head = new_item;
    } else {
      /* Find the tail of the queue and append new_item there. */
      EventQueueItem *iter = queue->head;

      while (iter->next != NULL) {
        iter = iter->next;
      }
      iter->next = new_item;
    }

    /* Tell the waiting thread that there's a new queue item. */
    signal_result = event_queue_signal(queue);
  }

  /* Re-queue the event handler so that the next occurrence of the event will
  ** trigger the handler again, rather than being ignored.
  ** If signalling the event queue raised an error, however, do not re-queue
  ** the event handler (effectively killing it). */
  if (signal_result >= 0) {
    if (_event_conduit_enqueue_handler(conduit, FALSE) == 0) {
      if (is_dummy_run) {
        conduit->status = dummy_run_complete;
      }
    }
  }

} /* end block that contains the actual callback functionality */
EVENT_CALLBACK_EXIT:
LEAVE_DB_WITHOUT_ENTERING_PYTHON /* Because we were never in Python. */
  return 0;
} /* _event_callback */


static int _event_conduit_enqueue_handler( EventConduitObject *conduit,
    boolean allowed_to_raise_exception
  )
{
  /* If $allowed_to_raise_exception is FALSE, this function will refrain from
  ** raising a Python exception, and will indicate its status solely via the
  ** return code. */

  /* WARNING:  This function places responsibility for the DB thread lock
  ** on the caller!  The caller must have acquired the DB lock before calling
  ** this function.  */

  int enqueue_result = isc_que_events(
      conduit->connection->status_vector,
      &(conduit->connection->db_handle),
      conduit->event_id,
      conduit->buffer_length,
      conduit->event_buffer,
      (isc_callback) _event_callback,
      conduit
    );

  if (allowed_to_raise_exception) {
    if ( DB_API_ERROR(conduit->connection->status_vector) ) {
      LEAVE_DB_WITHOUT_ENTERING_PYTHON
      raise_sql_exception( OperationalError,
          "Could not queue event handler: ", conduit->connection->status_vector
        );
      ENTER_DB_WITHOUT_LEAVING_PYTHON
    }
  }

  return enqueue_result;
} /* _event_conduit_enqueue_handler */


PyObject *abstract_wait(EventConduitObject *conduit, long timeout_millis) {
  EventQueue *queue = conduit->queue;
  EventQueueItem *q_item = NULL;
  PyObject *py_result = NULL;
  int wait_result = 0;

  ENTER_DB_WITHOUT_LEAVING_PYTHON

  /* If this conduit has never been wait()ed on before, we must kick off the
  ** event listening process. */
  if (conduit->status == none) {
    int enqueue_result = _event_conduit_enqueue_handler(conduit, TRUE);
    if (enqueue_result != 0) {
      /* _event_conduit_enqueue_handler already set Python exception. */
      goto PLATFORM_WAIT_ERROR;
    }
    conduit->status = after_initial_enqueue;
  }

  /* If there's NOT already an event waiting in the queue, wait for one.
  ** Otherwise, retrieve one from the queue and return without waiting. */
  if (queue->head == NULL) {
    LEAVE_DB_WITHOUT_ENTERING_PYTHON

    /* Release the Python GIL and wait for notification of the arrival of an
    ** item in the event queue. */
    LEAVE_PYTHON_WITHOUT_ENTERING_DB
    wait_result = event_queue_wait(queue, timeout_millis); /* We hold NO thread locks at this point. */
    ENTER_PYTHON_WITHOUT_LEAVING_DB

    ENTER_DB_WITHOUT_LEAVING_PYTHON
  }

  if (wait_result == EVENT_ERROR) {
    raise_exception(OperationalError, "Native event-wait encountered error.");
    goto PLATFORM_WAIT_ERROR;
  }

  if (wait_result == EVENT_TIMEOUT) {
    py_result = Py_None;
    Py_INCREF(Py_None);
  } else {
    /* Clear the event object no matter whether we had to wait() on it or not: */
    if ( event_queue_unsignal(queue) < 0 ) {
      raise_exception(OperationalError, "Could not unsignal native event object.");
      goto PLATFORM_WAIT_ERROR;
    }

    /* Retrieve the head of the event queue. */
    assert (queue->head != NULL);
    q_item = queue->head;
    queue->head = queue->head->next;
    /* Physically enfore the logical detachment of q_item from the queue: */
    q_item->next = NULL;

    /* Convert the raw q_item to a Python-accessible value. */
    py_result = _construct_event_count_dict(
        conduit->py_event_names, q_item->counts
      );

    /* The queue item must be garbage collected because it's no longer part
    ** of the queue; it will become unreachable when this function returns. */
    /* Plain free, rather than kimem_main_free, is appropriate here because
    ** we don't hold the GIL. */
    kimem_plain_free(q_item);
  }

  LEAVE_DB_WITHOUT_ENTERING_PYTHON /* We're already in Python. */
  return py_result;

PLATFORM_WAIT_ERROR:
  LEAVE_DB_WITHOUT_ENTERING_PYTHON /* We're already in Python. */
  return NULL;
} /* abstract_wait */


#endif /* ENABLE_DB_EVENT_SUPPORT */