Commit fa2fad3c authored by Tom Lane's avatar Tom Lane

Improve ilist.h's support for deletion of slist elements during iteration.

Previously one had to use slist_delete(), implying an additional scan of
the list, making this infrastructure considerably less efficient than
traditional Lists when deletion of element(s) in a long list is needed.
Modify the slist_foreach_modify() macro to support deleting the current
element in O(1) time, by keeping a "prev" pointer in addition to "cur"
and "next".  Although this makes iteration with this macro a bit slower,
no real harm is done, since in any scenario where you're not going to
delete the current list element you might as well just use slist_foreach
instead.  Improve the comments about when to use each macro.

Back-patch to 9.3 so that we'll have consistent semantics in all branches
that provide ilist.h.  Note this is an ABI break for callers of
slist_foreach_modify().

Andres Freund and Tom Lane
parent 910d3a45
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
* *
* It is not allowed to delete a 'node' which is is not in the list 'head' * It is not allowed to delete a 'node' which is is not in the list 'head'
* *
* Caution: this is O(n) * Caution: this is O(n); consider using slist_delete_current() instead.
*/ */
void void
slist_delete(slist_head *head, slist_node *node) slist_delete(slist_head *head, slist_node *node)
......
...@@ -267,15 +267,20 @@ BackgroundWorkerStateChange(void) ...@@ -267,15 +267,20 @@ BackgroundWorkerStateChange(void)
/* /*
* Forget about a background worker that's no longer needed. * Forget about a background worker that's no longer needed.
* *
* At present, this only happens when a background worker marked * The worker must be identified by passing an slist_mutable_iter that
* BGW_NEVER_RESTART exits. This function should only be invoked in * points to it. This convention allows deletion of workers during
* the postmaster. * searches of the worker list, and saves having to search the list again.
*
* This function must be invoked only in the postmaster.
*/ */
void void
ForgetBackgroundWorker(RegisteredBgWorker *rw) ForgetBackgroundWorker(slist_mutable_iter *cur)
{ {
RegisteredBgWorker *rw;
BackgroundWorkerSlot *slot; BackgroundWorkerSlot *slot;
rw = slist_container(RegisteredBgWorker, rw_lnode, cur->cur);
Assert(rw->rw_shmem_slot < max_worker_processes); Assert(rw->rw_shmem_slot < max_worker_processes);
slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot]; slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
slot->in_use = false; slot->in_use = false;
...@@ -284,7 +289,7 @@ ForgetBackgroundWorker(RegisteredBgWorker *rw) ...@@ -284,7 +289,7 @@ ForgetBackgroundWorker(RegisteredBgWorker *rw)
(errmsg("unregistering background worker: %s", (errmsg("unregistering background worker: %s",
rw->rw_worker.bgw_name))); rw->rw_worker.bgw_name)));
slist_delete(&BackgroundWorkerList, &rw->rw_lnode); slist_delete_current(cur);
free(rw); free(rw);
} }
......
...@@ -3602,6 +3602,13 @@ pgstat_write_statsfiles(bool permanent, bool allDbs) ...@@ -3602,6 +3602,13 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
{ {
slist_mutable_iter iter; slist_mutable_iter iter;
/*
* Strictly speaking we should do slist_delete_current() before
* freeing each request struct. We skip that and instead
* re-initialize the list header at the end. Nonetheless, we must use
* slist_foreach_modify, not just slist_foreach, since we will free
* the node's storage before advancing.
*/
slist_foreach_modify(iter, &last_statrequests) slist_foreach_modify(iter, &last_statrequests)
{ {
DBWriteRequest *req; DBWriteRequest *req;
......
...@@ -1452,7 +1452,7 @@ DetermineSleepTime(struct timeval * timeout) ...@@ -1452,7 +1452,7 @@ DetermineSleepTime(struct timeval * timeout)
if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART) if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
{ {
ForgetBackgroundWorker(rw); ForgetBackgroundWorker(&siter);
continue; continue;
} }
...@@ -5641,7 +5641,7 @@ StartOneBackgroundWorker(void) ...@@ -5641,7 +5641,7 @@ StartOneBackgroundWorker(void)
{ {
if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART) if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
{ {
ForgetBackgroundWorker(rw); ForgetBackgroundWorker(&iter);
continue; continue;
} }
......
...@@ -211,9 +211,14 @@ typedef struct slist_head ...@@ -211,9 +211,14 @@ typedef struct slist_head
* Used as state in slist_foreach(). To get the current element of the * Used as state in slist_foreach(). To get the current element of the
* iteration use the 'cur' member. * iteration use the 'cur' member.
* *
* Do *not* manipulate the list while iterating! * It's allowed to modify the list while iterating, with the exception of
* * deleting the iterator's current node; deletion of that node requires
* NB: this wouldn't really need to be an extra struct, we could use a * care if the iteration is to be continued afterward. (Doing so and also
* deleting or inserting adjacent list elements might misbehave; also, if
* the user frees the current node's storage, continuing the iteration is
* not safe.)
*
* NB: this wouldn't really need to be an extra struct, we could use an
* slist_node * directly. We prefer a separate type for consistency. * slist_node * directly. We prefer a separate type for consistency.
*/ */
typedef struct slist_iter typedef struct slist_iter
...@@ -224,15 +229,18 @@ typedef struct slist_iter ...@@ -224,15 +229,18 @@ typedef struct slist_iter
/* /*
* Singly linked list iterator allowing some modifications while iterating. * Singly linked list iterator allowing some modifications while iterating.
* *
* Used as state in slist_foreach_modify(). * Used as state in slist_foreach_modify(). To get the current element of the
* iteration use the 'cur' member.
* *
* Iterations using this are allowed to remove the current node and to add * The only list modification allowed while iterating is to remove the current
* more nodes ahead of the current node. * node via slist_delete_current() (*not* slist_delete()). Insertion or
* deletion of nodes adjacent to the current node would misbehave.
*/ */
typedef struct slist_mutable_iter typedef struct slist_mutable_iter
{ {
slist_node *cur; /* current element */ slist_node *cur; /* current element */
slist_node *next; /* next node we'll iterate to */ slist_node *next; /* next node we'll iterate to */
slist_node *prev; /* prev node, for deletions */
} slist_mutable_iter; } slist_mutable_iter;
...@@ -243,7 +251,7 @@ typedef struct slist_mutable_iter ...@@ -243,7 +251,7 @@ typedef struct slist_mutable_iter
/* Prototypes for functions too big to be inline */ /* Prototypes for functions too big to be inline */
/* Caution: this is O(n) */ /* Caution: this is O(n); consider using slist_delete_current() instead */
extern void slist_delete(slist_head *head, slist_node *node); extern void slist_delete(slist_head *head, slist_node *node);
#ifdef ILIST_DEBUG #ifdef ILIST_DEBUG
...@@ -578,6 +586,7 @@ extern slist_node *slist_pop_head_node(slist_head *head); ...@@ -578,6 +586,7 @@ extern slist_node *slist_pop_head_node(slist_head *head);
extern bool slist_has_next(slist_head *head, slist_node *node); extern bool slist_has_next(slist_head *head, slist_node *node);
extern slist_node *slist_next_node(slist_head *head, slist_node *node); extern slist_node *slist_next_node(slist_head *head, slist_node *node);
extern slist_node *slist_head_node(slist_head *head); extern slist_node *slist_head_node(slist_head *head);
extern void slist_delete_current(slist_mutable_iter *iter);
/* slist macro support function */ /* slist macro support function */
extern void *slist_head_element_off(slist_head *head, size_t off); extern void *slist_head_element_off(slist_head *head, size_t off);
...@@ -679,6 +688,29 @@ slist_head_node(slist_head *head) ...@@ -679,6 +688,29 @@ slist_head_node(slist_head *head)
{ {
return (slist_node *) slist_head_element_off(head, 0); return (slist_node *) slist_head_element_off(head, 0);
} }
/*
* Delete the list element the iterator currently points to.
*
* Caution: this modifies iter->cur, so don't use that again in the current
* loop iteration.
*/
STATIC_IF_INLINE void
slist_delete_current(slist_mutable_iter *iter)
{
/*
* Update previous element's forward link. If the iteration is at the
* first list element, iter->prev will point to the list header's "head"
* field, so we don't need a special case for that.
*/
iter->prev->next = iter->next;
/*
* Reset cur to prev, so that prev will continue to point to the prior
* valid list element after slist_foreach_modify() advances to the next.
*/
iter->cur = iter->prev;
}
#endif /* PG_USE_INLINE || ILIST_INCLUDE_DEFINITIONS */ #endif /* PG_USE_INLINE || ILIST_INCLUDE_DEFINITIONS */
/* /*
...@@ -706,7 +738,12 @@ slist_head_node(slist_head *head) ...@@ -706,7 +738,12 @@ slist_head_node(slist_head *head)
* *
* Access the current element with iter.cur. * Access the current element with iter.cur.
* *
* It is *not* allowed to manipulate the list during iteration. * It's allowed to modify the list while iterating, with the exception of
* deleting the iterator's current node; deletion of that node requires
* care if the iteration is to be continued afterward. (Doing so and also
* deleting or inserting adjacent list elements might misbehave; also, if
* the user frees the current node's storage, continuing the iteration is
* not safe.)
*/ */
#define slist_foreach(iter, lhead) \ #define slist_foreach(iter, lhead) \
for (AssertVariableIsOfTypeMacro(iter, slist_iter), \ for (AssertVariableIsOfTypeMacro(iter, slist_iter), \
...@@ -720,15 +757,18 @@ slist_head_node(slist_head *head) ...@@ -720,15 +757,18 @@ slist_head_node(slist_head *head)
* *
* Access the current element with iter.cur. * Access the current element with iter.cur.
* *
* Iterations using this are allowed to remove the current node and to add * The only list modification allowed while iterating is to remove the current
* more nodes ahead of the current node. * node via slist_delete_current() (*not* slist_delete()). Insertion or
* deletion of nodes adjacent to the current node would misbehave.
*/ */
#define slist_foreach_modify(iter, lhead) \ #define slist_foreach_modify(iter, lhead) \
for (AssertVariableIsOfTypeMacro(iter, slist_mutable_iter), \ for (AssertVariableIsOfTypeMacro(iter, slist_mutable_iter), \
AssertVariableIsOfTypeMacro(lhead, slist_head *), \ AssertVariableIsOfTypeMacro(lhead, slist_head *), \
(iter).cur = (lhead)->head.next, \ (iter).prev = &(lhead)->head, \
(iter).cur = (iter).prev->next, \
(iter).next = (iter).cur ? (iter).cur->next : NULL; \ (iter).next = (iter).cur ? (iter).cur->next : NULL; \
(iter).cur != NULL; \ (iter).cur != NULL; \
(iter).prev = (iter).cur, \
(iter).cur = (iter).next, \ (iter).cur = (iter).next, \
(iter).next = (iter).next ? (iter).next->next : NULL) (iter).next = (iter).next ? (iter).next->next : NULL)
......
...@@ -39,7 +39,7 @@ extern slist_head BackgroundWorkerList; ...@@ -39,7 +39,7 @@ extern slist_head BackgroundWorkerList;
extern Size BackgroundWorkerShmemSize(void); extern Size BackgroundWorkerShmemSize(void);
extern void BackgroundWorkerShmemInit(void); extern void BackgroundWorkerShmemInit(void);
extern void BackgroundWorkerStateChange(void); extern void BackgroundWorkerStateChange(void);
extern void ForgetBackgroundWorker(RegisteredBgWorker *); extern void ForgetBackgroundWorker(slist_mutable_iter *cur);
#ifdef EXEC_BACKEND #ifdef EXEC_BACKEND
extern BackgroundWorker *BackgroundWorkerEntry(int slotno); extern BackgroundWorker *BackgroundWorkerEntry(int slotno);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment