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
|
/* pmutil.h -- some helpful utilities for building midi
applications that use PortMidi
*/
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef void PmQueue;
/*
A single-reader, single-writer queue is created by
Pm_QueueCreate(), which takes the number of messages and
the message size as parameters. The queue only accepts
fixed sized messages. Returns NULL if memory cannot be allocated.
This queue implementation uses the "light pipe" algorithm which
operates correctly even with multi-processors and out-of-order
memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
Communication," Dr. Dobbs Portal, http://www.ddj.com/,
articleID=189401457, June 15, 2006. This algorithm requires
that messages be translated to a form where no words contain
zeros. Each word becomes its own "data valid" tag. Because of
this translation, we cannot return a pointer to data still in
the queue when the "peek" method is called. Instead, a buffer
is preallocated so that data can be copied there. Pm_QueuePeek()
dequeues a message into this buffer and returns a pointer to
it. A subsequent Pm_Dequeue() will copy from this buffer.
This implementation does not try to keep reader/writer data in
separate cache lines or prevent thrashing on cache lines.
However, this algorithm differs by doing inserts/removals in
units of messages rather than units of machine words. Some
performance improvement might be obtained by not clearing data
immediately after a read, but instead by waiting for the end
of the cache line, especially if messages are smaller than
cache lines. See the Dokumentov article for explanation.
The algorithm is extended to handle "overflow" reporting. To report
an overflow, the sender writes the current tail position to a field.
The receiver must acknowlege receipt by zeroing the field. The sender
will not send more until the field is zeroed.
Pm_QueueDestroy() destroys the queue and frees its storage.
*/
PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg);
PmError Pm_QueueDestroy(PmQueue *queue);
/*
Pm_Dequeue() removes one item from the queue, copying it into msg.
Returns 1 if successful, and 0 if the queue is empty.
Returns pmBufferOverflow if what would have been the next thing
in the queue was dropped due to overflow. (So when overflow occurs,
the receiver can receive a queue full of messages before getting the
overflow report. This protocol ensures that the reader will be
notified when data is lost due to overflow.
*/
PmError Pm_Dequeue(PmQueue *queue, void *msg);
/*
Pm_Enqueue() inserts one item into the queue, copying it from msg.
Returns pmNoError if successful and pmBufferOverflow if the queue was
already full. If pmBufferOverflow is returned, the overflow flag is set.
*/
PmError Pm_Enqueue(PmQueue *queue, void *msg);
/*
Pm_QueueFull() returns non-zero if the queue is full
Pm_QueueEmpty() returns non-zero if the queue is empty
Either condition may change immediately because a parallel
enqueue or dequeue operation could be in progress. Furthermore,
Pm_QueueEmpty() is optimistic: it may say false, when due to
out-of-order writes, the full message has not arrived. Therefore,
Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns
false. On the other hand, Pm_QueueFull() is pessimistic: if it
returns false, then Pm_Enqueue() is guaranteed to succeed.
*/
int Pm_QueueFull(PmQueue *queue);
int Pm_QueueEmpty(PmQueue *queue);
/*
Pm_QueuePeek() returns a pointer to the item at the head of the queue,
or NULL if the queue is empty. The item is not removed from the queue.
Pm_QueuePeek() will not indicate when an overflow occurs. If you want
to get and check pmBufferOverflow messages, use the return value of
Pm_QueuePeek() *only* as an indication that you should call
Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would
return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally
clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume
enqueuing messages. A subsequent call to Pm_QueuePeek()
will return a pointer to the first message *after* the overflow.
Using this as an indication to call Pm_Dequeue(), the first call
to Pm_Dequeue() will return pmBufferOverflow. The second call will
return success, copying the same message pointed to by the previous
Pm_QueuePeek().
When to use Pm_QueuePeek(): (1) when you need to look at the message
data to decide who should be called to receive it. (2) when you need
to know a message is ready but cannot accept the message.
Note that Pm_QueuePeek() is not a fast check, so if possible, you
might as well just call Pm_Dequeue() and accept the data if it is there.
*/
void *Pm_QueuePeek(PmQueue *queue);
/*
Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow
condition to the reader (dequeuer). E.g. when transfering data from
the OS to an application, if the OS indicates a buffer overrun,
Pm_SetOverflow() can be used to insure that the reader receives a
pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue
is NULL, returns pmBufferOverflow if buffer is already in an overflow
state, returns pmNoError if successfully set overflow state.
*/
PmError Pm_SetOverflow(PmQueue *queue);
#ifdef __cplusplus
}
#endif /* __cplusplus */
|