SELKIELogger  1.0.0
queue.c
1 /*
2  * Copyright (C) 2023 Swansea University
3  *
4  * This file is part of the SELKIELogger suite of tools.
5  *
6  * SELKIELogger is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation, either version 3 of the License, or (at your option)
9  * any later version.
10  *
11  * SELKIELogger is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this SELKIELogger product.
18  * If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 
24 #include "messages.h"
25 #include "queue.h"
26 
35 bool queue_init(msgqueue *queue) {
36  // Do not reinitialise valid or partially valid queue
37  if (queue->valid || queue->head || queue->tail) { return false; }
38  pthread_mutexattr_t ma = {0};
39  pthread_mutexattr_init(&ma);
40  pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK);
41  pthread_mutex_init(&(queue->lock), &ma);
42  pthread_mutexattr_destroy(&ma);
43 
44  if (pthread_mutex_lock(&(queue->lock))) {
45  // LCOV_EXCL_START
46  perror("queue_init");
47  return false;
48  // LCOV_EXCL_STOP
49  }
50  queue->head = NULL;
51  queue->tail = NULL;
52  queue->valid = true;
53  pthread_mutex_unlock(&(queue->lock));
54  return queue->valid;
55 }
56 
64 void queue_destroy(msgqueue *queue) {
65  queue->valid = false;
66  if (pthread_mutex_lock(&(queue->lock))) {
67  perror("queue_destroy"); //LCOV_EXCL_LINE
68  // Not returning, as we should still invalidate the queue
69  // Just means that we're already in an odd case
70  }
71  if (queue->head == NULL) {
72  // Queue is empty
73  // Tail should already be null, but set it just in case
74  queue->tail = NULL;
75  pthread_mutex_unlock(&(queue->lock));
76  pthread_mutex_destroy(&(queue->lock));
77  return;
78  }
79  queueitem *qi = queue->head;
80  while (qi) {
81  // Use message destroy to handle underlying storage
82  msg_destroy(qi->item);
83  free(qi->item);
84  qi->item = NULL;
85  queueitem *qin = qi->next;
86  free(qi);
87  qi = qin;
88  }
89  queue->head = NULL;
90  queue->tail = NULL;
91  pthread_mutex_unlock(&(queue->lock));
92  pthread_mutex_destroy(&(queue->lock));
93 }
94 
103 bool queue_push(msgqueue *queue, msg_t *msg) {
104  queueitem *qi = calloc(1, sizeof(queueitem));
105  qi->item = msg;
106  if (queue_push_qi(queue, qi)) {
107  return true;
108  } else {
109  //LCOV_EXCL_START
110  free(qi);
111  return false;
112  //LCOV_EXCL_STOP
113  }
114 }
115 
128 bool queue_push_qi(msgqueue *queue, queueitem *item) {
129  if (!queue->valid) { return false; }
130 
131  queueitem *qi = NULL;
132 
133  if (pthread_mutex_lock(&(queue->lock))) {
134  //LCOV_EXCL_START
135  perror("queue_push_qi");
136  return false;
137  //LCOV_EXCL_STOP
138  }
139 
140  // If head is NULL, the queue is empty, so point head and tail at our
141  // queueitem, unlock the queue and return.
142  if (queue->head == NULL) {
143  queue->head = item;
144  queue->tail = item;
145  pthread_mutex_unlock(&(queue->lock));
146  return true;
147  }
148 
149  // If head wasn't empty, find the tail
150  qi = queue->tail;
151  if (qi) {
152  // There is a slim chance that tail wasn't up to date, so follow queued
153  // items all the way down
154  if (qi->next) {
155  // qi->next is valid, so can re-assign it to qi
156  // The new qi->next is either valid (so loop and
157  // reassign), or NULL and we fall out of the loop
158  do {
159  qi = qi->next;
160  } while (qi->next);
161  }
162  // Once we've run out of valid qi->next pointers, we make our new item the
163  // end of the queue
164  qi->next = item;
165  }
166  // Update the tail pointer so the next push should jump direct to the end
167  // Note that item is a pointer, so assigning it to qi->next and queue->tail is
168  // valid
169  queue->tail = item;
170  pthread_mutex_unlock(&(queue->lock));
171  return true;
172 }
173 
187  int e = pthread_mutex_lock(&(queue->lock));
188  if (e != 0) {
189  //LCOV_EXCL_START
190  perror("queue_pop");
191  return NULL;
192  //LCOV_EXCL_STOP
193  }
194  queueitem *head = queue->head;
195  if (head == NULL || !queue->valid) {
196  // Empty or invalid queue
197  pthread_mutex_unlock(&(queue->lock));
198  return NULL;
199  }
200 
201  msg_t *item = head->item;
202  queue->head = head->next;
203 
204  head->next = NULL;
205  head->item = NULL;
206  if (queue->tail == head) { queue->tail = NULL; }
207  pthread_mutex_unlock(&(queue->lock));
208  // At this point we should have the only valid pointer to head
209  // ** This is only valid while a single thread is consuming items from the queue
210  // ** As with the explanation in queue_pop(), the cast keeps the compiler happy
211  free((struct queueitem *)head);
212  return item;
213 }
214 
219 int queue_count(const msgqueue *queue) {
220  if (!queue->valid) { return -1; }
221 
222  if (queue->head == NULL) { return 0; }
223 
224  int count = 1;
225  queueitem *item = queue->head;
226  while (item->next) {
227  count++;
228  item = item->next;
229  }
230  return count;
231 }
void msg_destroy(msg_t *msg)
Destroy a message.
Definition: messages.c:349
int queue_count(const msgqueue *queue)
Iterate over queue and return current number of items.
Definition: queue.c:219
bool queue_push(msgqueue *queue, msg_t *msg)
Add a message to the tail of the queue.
Definition: queue.c:103
msg_t * queue_pop(msgqueue *queue)
Remove topmost item from the queue and return it, if queue is not empty.
Definition: queue.c:186
bool queue_push_qi(msgqueue *queue, queueitem *item)
Add a queue item to the tail of the queue.
Definition: queue.c:128
void queue_destroy(msgqueue *queue)
Invalidate queue and destroy all contents.
Definition: queue.c:64
bool queue_init(msgqueue *queue)
Ensure queue structure is set to known good values and marked valid.
Definition: queue.c:35
Queuable message.
Definition: messages.h:71
Represent a simple FIFO message queue.
Definition: queue.h:51
queueitem * head
Points to first message, or NULL if empty.
Definition: queue.h:52
bool valid
Queue status.
Definition: queue.h:55
pthread_mutex_t lock
Queue lock.
Definition: queue.h:54
queueitem * tail
brief Tail entry hint
Definition: queue.h:53
msg_t * item
Queued message.
Definition: queue.h:68
queueitem * next
Pointer to next item, or NULL for queue tail.
Definition: queue.h:69