| /*------------------------------------------------------------------------- |
| * |
| * gdddetector.c |
| * Global DeadLock Detector - Detector Algorithm |
| * |
| * |
| * Copyright (c) 2018-Present VMware, Inc. or its affiliates. |
| * |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include "storage/procarray.h" |
| |
| #include "gdddetector.h" |
| #include "gdddetectorpriv.h" |
| |
| /***************************************************************************/ |
| |
| static GddGraph *gddCtxGetGraph(GddCtx *ctx, int segid); |
| static GddStat *gddCtxGetGlobalStat(GddCtx *ctx, DistributedTransactionId vid); |
| static void gddCtxRemoveVid(GddCtx *ctx, DistributedTransactionId vid); |
| static int gddCtxGetMaxVid(GddCtx *ctx); |
| |
| static GddStat *gddStatNew(DistributedTransactionId vid); |
| static void gddStatInit(GddStat *stat, DistributedTransactionId vid); |
| |
| static GddGraph *gddGraphNew(int id); |
| static GddVert *gddGraphGetVert(GddGraph *graph, DistributedTransactionId vid); |
| static GddEdge *gddGraphMakeEdge(GddGraph *graph, DistributedTransactionId from, DistributedTransactionId to, bool solid); |
| static void gddGraphRemoveVid(GddGraph *graph, DistributedTransactionId vid); |
| |
| static GddVert *gddVertNew(DistributedTransactionId id); |
| static void gddVertBindStats(GddVert *vert, GddStat *global, GddStat *topstat); |
| static bool gddVertUnlinkAll(GddVert *vert); |
| static bool gddVertReduce(GddVert *vert); |
| static int gddVertGetInDegree(GddVert *vert); |
| static int gddVertGetOutDegree(GddVert *vert); |
| |
| static GddEdge *gddEdgeNew(GddVert *from, GddVert *to, bool solid); |
| static void gddEdgeLink(GddEdge *edge); |
| static void gddEdgeUnlink(GddEdge *edge, GddListIter *fromiter, GddListIter *toiter); |
| static void gddEdgeUnlinkFrom(GddEdge *edge, GddListIter *fromiter); |
| static void gddEdgeUnlinkTo(GddEdge *edge, GddListIter *toiter); |
| |
| static void gddMapInit(GddMap *map); |
| static void gddMapEnsureCapacity(GddMap *map, int capacity); |
| static GddPair *gddMapGetPair(GddMap *map, DistributedTransactionId key); |
| static void *gddMapGet(GddMap *map, DistributedTransactionId key); |
| static void gddMapSetUnsafe(GddMap *map, DistributedTransactionId key, void *ptr); |
| |
| /***************************************************************************/ |
| |
| /* |
| * Create a new Global DeadLock Detector context. |
| */ |
| GddCtx * |
| GddCtxNew(void) |
| { |
| GddCtx *ctx = palloc(sizeof(*ctx)); |
| |
| gddStatInit(&ctx->topstat, 0); |
| gddMapInit(&ctx->globals); |
| gddMapInit(&ctx->graphs); |
| |
| return ctx; |
| } |
| |
| /* |
| * Add an edge to one local graph. |
| * |
| * Return the edge. |
| */ |
| GddEdge * |
| GddCtxAddEdge(GddCtx *ctx, int segid, DistributedTransactionId from, DistributedTransactionId to, bool solid) |
| { |
| GddEdge *edge; |
| GddGraph *graph; |
| GddStat *global; |
| |
| Assert(ctx != NULL); |
| |
| graph = gddCtxGetGraph(ctx, segid); |
| edge = gddGraphMakeEdge(graph, from, to, solid); |
| |
| global = gddCtxGetGlobalStat(ctx, from); |
| gddVertBindStats(edge->from, global, &ctx->topstat); |
| |
| global = gddCtxGetGlobalStat(ctx, to); |
| gddVertBindStats(edge->to, global, &ctx->topstat); |
| |
| /* |
| * Actually link the edge to its from/to verts, |
| * and update global in/out degrees. |
| */ |
| gddEdgeLink(edge); |
| |
| return edge; |
| } |
| |
| /* |
| * Reduce verts and edges looply until nothing can be deleted. |
| */ |
| void |
| GddCtxReduce(GddCtx *ctx) |
| { |
| GddMapIter graphiter; |
| GddMapIter vertiter; |
| bool dirty = true; |
| |
| Assert(ctx != NULL); |
| |
| while (dirty) |
| { |
| dirty = false; |
| |
| gdd_ctx_foreach_vert(graphiter, vertiter, ctx) |
| { |
| GddVert *vert = gdd_map_iter_get_ptr(vertiter); |
| |
| if (gddVertReduce(vert)) |
| dirty = true; |
| |
| /* |
| * Remove vert from the graph if no edges left. |
| * |
| * We want to do this even if gddVertReduce() returns false |
| * as vert1's last edge might be removed in vert2's reduce call. |
| * However depending on the order of vert1 and vert2, we can not |
| * promise all the empty verts are removed, so do not make the |
| * assumption on this. See gddCtxGetMaxVid() for example. |
| */ |
| if (!gddVertGetInDegree(vert) && !gddVertGetOutDegree(vert)) |
| gdd_map_iter_delete(vertiter); |
| } |
| } |
| } |
| |
| /* |
| * Break deadlocks and return a list of vids to cancel. |
| */ |
| List * |
| GddCtxBreakDeadLock(GddCtx *ctx) |
| { |
| List *vids = NIL; |
| |
| Assert(ctx != NULL); |
| |
| while (!GddCtxEmpty(ctx)) |
| { |
| /* |
| * The only policy supported for now is to cancel the youngest vert, |
| * who has the max vid. |
| */ |
| DistributedTransactionId *maxvid = palloc(sizeof(DistributedTransactionId)); |
| *maxvid = gddCtxGetMaxVid(ctx); |
| |
| vids = lappend(vids, maxvid); |
| |
| /* |
| * Cancel this vert and reduce again to see if more deadlocks |
| * are detected. |
| */ |
| gddCtxRemoveVid(ctx, *maxvid); |
| GddCtxReduce(ctx); |
| } |
| |
| return vids; |
| } |
| |
| /* |
| * Return true if no edges left. |
| */ |
| bool |
| GddCtxEmpty(GddCtx *ctx) |
| { |
| Assert(ctx->topstat.indeg == ctx->topstat.outdeg); |
| |
| return ctx->topstat.indeg == 0; |
| } |
| |
| /***************************************************************************/ |
| |
| /* |
| * Get the graph with segment id. |
| * |
| * A new one is created if it's first seen. |
| */ |
| static GddGraph * |
| gddCtxGetGraph(GddCtx *ctx, int segid) |
| { |
| GddGraph *graph; |
| |
| Assert(ctx != NULL); |
| |
| graph = gddMapGet(&ctx->graphs, segid); |
| |
| if (!graph) |
| { |
| graph = gddGraphNew(segid); |
| gddMapSetUnsafe(&ctx->graphs, segid, graph); |
| } |
| |
| return graph; |
| } |
| |
| /* |
| * Get the global with vert id. |
| * |
| * A new one is created if it's first seen. |
| */ |
| static GddStat * |
| gddCtxGetGlobalStat(GddCtx *ctx, DistributedTransactionId vid) |
| { |
| GddStat *global; |
| |
| Assert(ctx != NULL); |
| |
| global = gddMapGet(&ctx->globals, vid); |
| |
| if (!global) |
| { |
| global = gddStatNew(vid); |
| gddMapSetUnsafe(&ctx->globals, vid, global); |
| } |
| |
| return global; |
| } |
| |
| /* |
| * Remove verts whose vert id equal to vid on all local graphs. |
| */ |
| static void |
| gddCtxRemoveVid(GddCtx *ctx, DistributedTransactionId vid) |
| { |
| GddMapIter iter; |
| |
| Assert(ctx != NULL); |
| |
| /* Do not use gdd_ctx_foreach_vert() as break is used in the inner loop */ |
| gdd_ctx_foreach_graph(iter, ctx) |
| { |
| GddGraph *graph = gdd_map_iter_get_ptr(iter); |
| |
| gddGraphRemoveVid(graph, vid); |
| } |
| } |
| |
| /* |
| * Get the max vert id, return 0 if no vert left at all. |
| */ |
| static int |
| gddCtxGetMaxVid(GddCtx *ctx) |
| { |
| GddMapIter graphiter; |
| GddMapIter vertiter; |
| DistributedTransactionId maxvid = 0; |
| |
| Assert(ctx != NULL); |
| |
| gdd_ctx_foreach_vert(graphiter, vertiter, ctx) |
| { |
| GddVert *vert = gdd_map_iter_get_ptr(vertiter); |
| |
| /* |
| * It's possible for vert to have zero in/out edge, |
| * which means vert was already deleted in the |
| * algorithm conception so should be skipped. |
| */ |
| if (gddVertGetInDegree(vert) || gddVertGetOutDegree(vert)) |
| maxvid = Max(maxvid, vert->id); |
| } |
| |
| return maxvid; |
| } |
| |
| /***************************************************************************/ |
| |
| /* |
| * Create a new global struct. |
| */ |
| static GddStat * |
| gddStatNew(DistributedTransactionId vid) |
| { |
| GddStat *stat = palloc(sizeof(*stat)); |
| |
| gddStatInit(stat, vid); |
| |
| return stat; |
| } |
| |
| /* |
| * Initialize a global struct. |
| */ |
| static void |
| gddStatInit(GddStat *stat, DistributedTransactionId vid) |
| { |
| Assert(stat != NULL); |
| |
| stat->id = vid; |
| stat->indeg = 0; |
| stat->outdeg = 0; |
| } |
| |
| /***************************************************************************/ |
| |
| /* |
| * Create a new graph struct. |
| */ |
| static GddGraph * |
| gddGraphNew(int id) |
| { |
| GddGraph *graph = palloc(sizeof(*graph)); |
| |
| graph->id = id; |
| |
| gddMapInit(&graph->verts); |
| |
| return graph; |
| } |
| |
| /* |
| * Get the vert with vert id. |
| * |
| * A new one is created if it's first seen. |
| */ |
| static GddVert * |
| gddGraphGetVert(GddGraph *graph, DistributedTransactionId vid) |
| { |
| GddVert *vert; |
| |
| Assert(graph != NULL); |
| |
| vert = gddMapGet(&graph->verts, vid); |
| |
| if (!vert) |
| { |
| vert = gddVertNew(vid); |
| gddMapSetUnsafe(&graph->verts, vid, vert); |
| } |
| |
| return vert; |
| } |
| |
| /* |
| * Make an edge in graph. |
| * |
| * The edge is not linked yet. |
| */ |
| static GddEdge * |
| gddGraphMakeEdge(GddGraph *graph, DistributedTransactionId from, DistributedTransactionId to, bool solid) |
| { |
| GddEdge *edge; |
| GddVert *vfrom; |
| GddVert *vto; |
| |
| Assert(graph != NULL); |
| |
| vfrom = gddGraphGetVert(graph, from); |
| vto = gddGraphGetVert(graph, to); |
| |
| edge = gddEdgeNew(vfrom, vto, solid); |
| |
| return edge; |
| } |
| |
| /* |
| * Remove the vert whose vert id is vid. |
| */ |
| static void |
| gddGraphRemoveVid(GddGraph *graph, DistributedTransactionId vid) |
| { |
| GddMapIter iter; |
| |
| Assert(graph != NULL); |
| |
| gdd_graph_foreach_vert(iter, graph) |
| { |
| GddVert *vert = gdd_map_iter_get_ptr(iter); |
| |
| Assert(vert != NULL); |
| |
| if (vert->id != vid) |
| continue; |
| |
| /* Remove all local in/out edges */ |
| gddVertUnlinkAll(vert); |
| |
| /* Finally remove vert from graph */ |
| gdd_map_iter_delete(iter); |
| |
| /* Only one vert could have vert id equals to vid */ |
| break; |
| } |
| } |
| |
| /***************************************************************************/ |
| |
| /* |
| * Create a new vert. |
| */ |
| static GddVert * |
| gddVertNew(DistributedTransactionId id) |
| { |
| GddVert *vert = palloc(sizeof(*vert)); |
| |
| vert->id = id; |
| vert->edgesIn = NIL; |
| vert->edgesOut = NIL; |
| vert->data = NULL; |
| |
| return vert; |
| } |
| |
| /* |
| * Bind global and vert. |
| */ |
| static void |
| gddVertBindStats(GddVert *vert, GddStat *global, GddStat *topstat) |
| { |
| Assert(vert != NULL); |
| |
| vert->global = global; |
| vert->topstat = topstat; |
| } |
| |
| /* |
| * Unlink vert's all edges. |
| */ |
| static bool |
| gddVertUnlinkAll(GddVert *vert) |
| { |
| GddListIter edgeiter; |
| bool dirty = false; |
| |
| Assert(vert != NULL); |
| |
| gdd_vert_foreach_in_edge(edgeiter, vert) |
| { |
| GddEdge *edge = gdd_list_iter_get_ptr(edgeiter); |
| |
| gddEdgeUnlink(edge, NULL, &edgeiter); |
| dirty = true; |
| } |
| |
| Assert(vert->edgesIn == NIL); |
| |
| gdd_vert_foreach_out_edge(edgeiter, vert) |
| { |
| GddEdge *edge = gdd_list_iter_get_ptr(edgeiter); |
| |
| gddEdgeUnlink(edge, &edgeiter, NULL); |
| dirty = true; |
| } |
| |
| Assert(vert->edgesOut == NIL); |
| |
| return dirty; |
| } |
| |
| /* |
| * Reduce vert's edges. |
| * |
| * The algorithm is described at README.md. |
| * |
| * Return true if anything is deleted. |
| */ |
| static bool |
| gddVertReduce(GddVert *vert) |
| { |
| GddStat *global; |
| bool dirty = false; |
| |
| Assert(vert != NULL); |
| Assert(vert->global != NULL); |
| |
| global = vert->global; |
| |
| /* |
| * Remove global end verts (verts with global 0 in/out degrees) |
| */ |
| if (global->indeg == 0 || global->outdeg == 0) |
| { |
| /* Remove all local in/out edges */ |
| if (gddVertUnlinkAll(vert)) |
| dirty = true; |
| |
| return dirty; |
| } |
| |
| /* |
| * Remove a vert's dotted in edges if vert's local out degree is 0. |
| */ |
| if (gddVertGetOutDegree(vert) == 0) |
| { |
| GddListIter edgeiter; |
| |
| gdd_vert_foreach_in_edge(edgeiter, vert) |
| { |
| GddEdge *edge = gdd_list_iter_get_ptr(edgeiter); |
| |
| /* Do not remove solid edges */ |
| if (edge->solid) |
| continue; |
| |
| gddEdgeUnlink(edge, NULL, &edgeiter); |
| dirty = true; |
| } |
| |
| AssertImply(gdd_list_iter_get_list(edgeiter) == NIL, |
| vert->edgesIn == NIL); |
| } |
| |
| return dirty; |
| } |
| |
| /* |
| * Get vert's local in degree. |
| */ |
| static int |
| gddVertGetInDegree(GddVert *vert) |
| { |
| Assert(vert != NULL); |
| |
| return list_length(vert->edgesIn); |
| } |
| |
| /* |
| * Get vert's local in degree. |
| */ |
| static int |
| gddVertGetOutDegree(GddVert *vert) |
| { |
| Assert(vert != NULL); |
| |
| return list_length(vert->edgesOut); |
| } |
| |
| /***************************************************************************/ |
| |
| /* |
| * Create a new edge. |
| */ |
| static GddEdge * |
| gddEdgeNew(GddVert *from, GddVert *to, bool solid) |
| { |
| GddEdge *edge = palloc(sizeof(*edge)); |
| |
| edge->from = from; |
| edge->to = to; |
| edge->solid = solid; |
| |
| return edge; |
| } |
| |
| /* |
| * Link edge to its from/to verts and update global in/out degrees. |
| */ |
| static void |
| gddEdgeLink(GddEdge *edge) |
| { |
| Assert(edge != NULL); |
| Assert(edge->from != NULL); |
| Assert(edge->from->global != NULL); |
| Assert(edge->to != NULL); |
| Assert(edge->to->global != NULL); |
| Assert(edge->to->topstat != NULL); |
| Assert(edge->to->topstat == edge->from->topstat); |
| |
| edge->from->topstat->outdeg++; |
| edge->to->topstat->indeg++; |
| Assert(edge->from->topstat->outdeg == edge->to->topstat->indeg); |
| |
| /* Update global in/out degrees */ |
| edge->from->global->outdeg++; |
| edge->to->global->indeg++; |
| |
| edge->from->edgesOut = lappend(edge->from->edgesOut, edge); |
| edge->to->edgesIn = lappend(edge->to->edgesIn, edge); |
| } |
| |
| /* |
| * Unlink edge from its from/to verts and update global in/out degrees. |
| */ |
| static void |
| gddEdgeUnlink(GddEdge *edge, GddListIter *fromiter, GddListIter *toiter) |
| { |
| Assert(edge != NULL); |
| |
| gddEdgeUnlinkFrom(edge, fromiter); |
| gddEdgeUnlinkTo(edge, toiter); |
| |
| Assert(edge->from->topstat->outdeg == edge->to->topstat->indeg); |
| } |
| |
| /* |
| * Unlink edge from its from vert and update global out degree. |
| */ |
| static void |
| gddEdgeUnlinkFrom(GddEdge *edge, GddListIter *fromiter) |
| { |
| Assert(edge != NULL); |
| Assert(edge->from != NULL); |
| Assert(edge->from->global != NULL); |
| Assert(edge->from->topstat != NULL); |
| |
| edge->from->topstat->outdeg--; |
| Assert(edge->from->topstat->outdeg >= 0); |
| |
| edge->from->global->outdeg--; |
| Assert(edge->from->global->outdeg >= 0); |
| |
| /* |
| * If the from vert's iter is provided then delete it in-place, |
| * otherwise delete it with a scan. |
| */ |
| if (fromiter) |
| { |
| gdd_list_iter_delete(*fromiter); |
| edge->from->edgesOut = gdd_list_iter_get_list(*fromiter); |
| } |
| else |
| edge->from->edgesOut = list_delete_ptr(edge->from->edgesOut, edge); |
| } |
| |
| /* |
| * Unlink edge from its to vert and update global in degree. |
| */ |
| static void |
| gddEdgeUnlinkTo(GddEdge *edge, GddListIter *toiter) |
| { |
| Assert(edge != NULL); |
| Assert(edge->to != NULL); |
| Assert(edge->to->global != NULL); |
| Assert(edge->to->topstat != NULL); |
| |
| edge->to->topstat->indeg--; |
| Assert(edge->to->topstat->indeg >= 0); |
| |
| edge->to->global->indeg--; |
| Assert(edge->to->global->indeg >= 0); |
| |
| /* |
| * If the to vert's iter is provided then delete it in-place, |
| * otherwise delete it with a scan. |
| */ |
| if (toiter) |
| { |
| gdd_list_iter_delete(*toiter); |
| edge->to->edgesIn = gdd_list_iter_get_list(*toiter); |
| } |
| else |
| edge->to->edgesIn = list_delete_ptr(edge->to->edgesIn, edge); |
| } |
| |
| /***************************************************************************/ |
| |
| /* |
| * Initialize a map. |
| */ |
| static void |
| gddMapInit(GddMap *map) |
| { |
| Assert(map != NULL); |
| |
| map->length = 0; |
| map->capacity = 4; |
| map->pairs = palloc(map->capacity * sizeof(GddPair)); |
| } |
| |
| /* |
| * Ensure map has enough capacity. |
| */ |
| static void |
| gddMapEnsureCapacity(GddMap *map, int capacity) |
| { |
| Assert(map != NULL); |
| |
| if (capacity <= map->capacity) |
| return; |
| |
| /* To prevent frequently resizing we prepare double capacity as required */ |
| map->capacity = capacity << 1; |
| |
| map->pairs = repalloc(map->pairs, |
| map->capacity * sizeof(GddPair)); |
| } |
| |
| /* |
| * Lookup the pair struct by key. |
| * |
| * Return NULL if key is not found. |
| */ |
| static GddPair * |
| gddMapGetPair(GddMap *map, DistributedTransactionId key) |
| { |
| int i; |
| |
| Assert(map != NULL); |
| |
| for (i = 0; i < map->length; i++) |
| { |
| GddPair *pair = &map->pairs[i]; |
| |
| if (pair->key == key) |
| return pair; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Lookup the pointer by key. |
| * |
| * Return NULL if key is not found. If NULL is valid value for the pointer |
| * then use gddMapGetPair() instead of this one. |
| */ |
| static void * |
| gddMapGet(GddMap *map, DistributedTransactionId key) |
| { |
| GddPair *pair = gddMapGetPair(map, key); |
| |
| return pair ? pair->ptr : NULL; |
| } |
| |
| /* |
| * Append a new <k,v> pair without checking for existence. |
| * |
| * Only use this if you have already checked with the gddMapGet*() functions |
| * that k does not exist. |
| */ |
| static void |
| gddMapSetUnsafe(GddMap *map, DistributedTransactionId key, void *ptr) |
| { |
| GddPair *pair; |
| |
| gddMapEnsureCapacity(map, map->length + 1); |
| |
| pair = &map->pairs[map->length++]; |
| |
| pair->key = key; |
| pair->ptr = ptr; |
| } |