From 13cc68f926325797782eacb953d9817dac0ee61a Mon Sep 17 00:00:00 2001 From: WangXiuqiang Date: Wed, 6 Apr 2022 16:13:49 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=85=BC=E5=AE=B9mysql=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E9=94=81=EF=BC=8C=E5=AE=9E=E7=8E=B0=E6=94=AF=E6=8C=81=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E7=9A=84advisory=E7=94=A8=E6=88=B7=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contrib/b_sql_plugin/plugin_postgres.cpp | 13 +- .../plugin_utils/adt/lockfuncs.cpp | 387 ++++++++++++++++++ contrib/b_sql_plugin/sql/mysqllock_test.7z | Bin 0 -> 31449 bytes contrib/b_sql_plugin/sql_script/lockfuncs.sql | 42 ++ 4 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 contrib/b_sql_plugin/sql/mysqllock_test.7z create mode 100644 contrib/b_sql_plugin/sql_script/lockfuncs.sql diff --git a/contrib/b_sql_plugin/plugin_postgres.cpp b/contrib/b_sql_plugin/plugin_postgres.cpp index 373c717df..a7d57dd63 100644 --- a/contrib/b_sql_plugin/plugin_postgres.cpp +++ b/contrib/b_sql_plugin/plugin_postgres.cpp @@ -87,6 +87,10 @@ static ExecNodes* assign_utility_stmt_exec_nodes(Node* parse_tree); PG_MODULE_MAGIC_PUBLIC; + +extern void InitLockNameHash(); +extern struct HTAB* lockNameHash; +extern pthread_mutex_t gNameHashLock; extern void initBSQLBuiltinFuncs(); extern struct HTAB* b_nameHash; extern struct HTAB* b_oidHash; @@ -200,6 +204,13 @@ void _PG_init(void) if (b_oidHash == NULL || b_nameHash == NULL) { initBSQLBuiltinFuncs(); } + + AutoMutexLock nameHashLock(&gNameHashLock); + nameHashLock.lock(); + if (lockNameHash == NULL) + InitLockNameHash(); + nameHashLock.unLock(); + g_instance.raw_parser_hook[DB_CMPT_B] = (void*)raw_parser; init_plugin_object(); } @@ -6955,4 +6966,4 @@ void init_session_vars(void) PGC_USERSET, 0, NULL, NULL, NULL); -} \ No newline at end of file +} diff --git a/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp b/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp index 9da99361a..ad1817bcd 100644 --- a/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp +++ b/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp @@ -20,6 +20,7 @@ #include "catalog/pg_database.h" #include "catalog/indexing.h" #include "plugin_commands/dbcommands.h" +#include "plugin_postgres.h" #include "funcapi.h" #include "miscadmin.h" #ifdef PGXC @@ -38,6 +39,95 @@ #include "utils/syscache.h" #include "utils/snapmgr.h" #include "pgstat.h" +#include "libpq/md5.h" + +/* + * user advisory lock like MySQL. + * different from openGauss: + * 1. lock using string, not int. + * 2. lokc allow timeout + * using md5sum parse string as int8*16, then cast to int32*4,using locktag_field2-5 + */ +/* + * get_lock get advisory lock using string. + * arg1: string length < 64 + * arg2: int timeout ,seconds,default 0 ,waiting forever + * return: 1 if success, 0 if timeout, null if other error + */ +#define SET_LOCKTAG_ADVISORY_5(locktag, id1, id2, id3, id4, id5) \ + ((locktag).locktag_field1 = (id1), (locktag).locktag_field2 = (id2), (locktag).locktag_field3 = (id3), \ + (locktag).locktag_field4 = (id4), (locktag).locktag_field5 = (id5), (locktag).locktag_type = LOCKTAG_ADVISORY, \ + (locktag).locktag_lockmethodid = USER_LOCKMETHOD) +#define MAX_ADVISORY_LOCK_NAME_LENTH 64 + +struct HTAB* lockNameHash = NULL; + +pthread_mutex_t gNameHashLock; + +typedef struct LockName { + char name[MAX_ADVISORY_LOCK_NAME_LENTH+1]; + int64 nlocks; + char reserved[63]; +} LockName; + +void InitLockNameHash() +{ + int hashFlags; + HASHCTL info = {0}; + info.keysize = MAX_ADVISORY_LOCK_NAME_LENTH+1; + info.entrysize = sizeof(LockName); + info.hash = string_hash; + info.hcxt = g_instance.builtin_proc_context; + hashFlags = (HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + lockNameHash = hash_create("Lock Name Hash", 100, &info, hashFlags); +} + +static void SetLockNameHash(const char* name, bool releaseFlag = false) +{ + char tempName[MAX_ADVISORY_LOCK_NAME_LENTH+1] = {0}; + int rc = strncpy_s((char*)tempName, MAX_ADVISORY_LOCK_NAME_LENTH+1, name, strlen(name)); + securec_check(rc, "\0", "\0"); + LockName *result = NULL; + bool found = false; + + Assert(name != NULL); + + AutoMutexLock nameHashLock(&gNameHashLock); + nameHashLock.lock(); + result = (LockName *)hash_search(lockNameHash, (void *)&tempName, HASH_ENTER, &found); + if (releaseFlag && found) { + result->nlocks--; + if (result->nlocks == 0) + hash_search(lockNameHash, (void *)&(result->name), HASH_REMOVE, NULL); + } else if (!releaseFlag) { + if (found) + result->nlocks++; + else + result->nlocks = 1; + } + nameHashLock.unLock(); +} + +static int64 LockNameHashRelease() +{ + HASH_SEQ_STATUS status; + LockName *lockName = NULL; + int64 sumLocks = 0; + + AutoMutexLock nameHashLock(&gNameHashLock); + nameHashLock.lock(); + hash_seq_init(&status, lockNameHash); + + while ((lockName = (LockName *)hash_seq_search(&status)) != NULL) { + sumLocks += lockName->nlocks; + lockName->nlocks = 0; + hash_search(lockNameHash, (void *)&(lockName->name), HASH_REMOVE, NULL); + } + nameHashLock.unLock(); + + return sumLocks; +} #define NUM_LOCKTAG_ID 17 /* This must match enum LockTagType! */ @@ -1284,6 +1374,303 @@ Datum pg_advisory_unlock_all(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } + +PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLockWithtimeTextFormat); +extern "C" DLL_PUBLIC Datum GetAdvisoryLockWithtimeTextFormat(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLockWithtime); +extern "C" DLL_PUBLIC Datum GetAdvisoryLockWithtime(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLock); +extern "C" DLL_PUBLIC Datum GetAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(ReleaseAdvisoryLock); +extern "C" DLL_PUBLIC Datum ReleaseAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(IsFreeAdvisoryLock); +extern "C" DLL_PUBLIC Datum IsFreeAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(IsUsedAdvisoryLock); +extern "C" DLL_PUBLIC Datum IsUsedAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(ReleaseAllAdvisoryLock); +extern "C" DLL_PUBLIC Datum ReleaseAllAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(GetAllAdvisoryLock); +extern "C" DLL_PUBLIC Datum GetAllAdvisoryLock(PG_FUNCTION_ARGS); + +uint32 *AdvisoryLockGetIdFromName(char *name, uint32 *lockid) +{ + uint8 sum[16]; + if (strlen(name) > MAX_ADVISORY_LOCK_NAME_LENTH) { + ereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG), errmsg("max length of advisory lock is 64"))); + } + if (strlen(name) == 0) { + ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("Incorrect user-level lock name '%s'", name))); + } + + pg_md5_binary(name, strlen(name), sum); + + // merge uint8 sum[16] into uint32 lockid[4] + for (int i = 0; i < 4; i++) { + int index = i * 4; + for (int j = 0; j < 4; j++) { + uint32 t = sum[index + j]; + lockid[i] = lockid[i] << 8; + lockid[i] |= t; + } + } + return lockid; +} + +int GetAdvisoryLockWithTimeInner(char *lockname, double timeout) +{ + + TimestampTz startTime; + LOCKTAG tag; + uint32 lockid[4] = {0}; + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + if (t_thrd.shemem_ptr_cxt.mySessionTimeEntry) { + startTime = GetCurrentTimestamp(); + } else { + ereport(WARNING, (errmsg("TimeEntry not inited"))); + return -1; + } + if (timeout <= 0) { + LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, false, false); + if (res == LOCKACQUIRE_NOT_AVAIL) { + return 0; + } else { + SetLockNameHash(lockname); + return 1; + } + } else { + do { + LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, true, false); + if (res == LOCKACQUIRE_NOT_AVAIL) { + pg_usleep(USECS_PER_MSEC); + ProcessInterrupts(); + } else { + SetLockNameHash(lockname); + return 1; + } + } while (ComputeTimeStamp(startTime) / MSECS_PER_SEC < timeout); + } + return 0; +} + +Datum GetAdvisoryLockWithtimeTextFormat(PG_FUNCTION_ARGS) +{ + char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *timeoutStr = text_to_cstring(PG_GETARG_TEXT_PP(1)); + char *ptr; + double timeout = strtod(timeoutStr, &ptr); + if (strlen(ptr) > 0) { + ereport(WARNING, (errmsg("truncated incorrect value: %s", ptr))); + } + int ret = GetAdvisoryLockWithTimeInner(lockname, timeout); + if (ret == -1) { + PG_RETURN_NULL(); + } + PG_RETURN_INT64(ret); +} + +Datum GetAdvisoryLockWithtime(PG_FUNCTION_ARGS) +{ + char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + double timeout = PG_GETARG_FLOAT8(1); + int ret = GetAdvisoryLockWithTimeInner(lockname, timeout); + if (ret == -1) { + PG_RETURN_NULL(); + } + PG_RETURN_INT32(ret); +} + +Datum GetAdvisoryLock(PG_FUNCTION_ARGS) +{ + char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + LOCKTAG tag; + uint32 lockid[4] = {0}; + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + + LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, false, false); + if (res == LOCKACQUIRE_NOT_AVAIL) { + PG_RETURN_INT32(0); + } else { + SetLockNameHash(lockname); + PG_RETURN_INT32(1); + } +} + +bool IsFreeAdvisoryLockInner(char *lockname) +{ + LOCKTAG tag; + uint32 lockid[4] = {0}; + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + uint32 hashcode = LockTagHashCode(&tag); + LWLock *partitionLock = NULL; + partitionLock = LockHashPartitionLock(hashcode); + LWLockAcquire(partitionLock, LW_SHARED); + bool foundCachedEntry = false; + hash_search_with_hash_value(t_thrd.storage_cxt.LockMethodLockHash, &tag, hashcode, HASH_FIND, &foundCachedEntry); + LWLockRelease(partitionLock); + return !foundCachedEntry; +} + +Datum ReleaseAdvisoryLock(PG_FUNCTION_ARGS) +{ + char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + LOCKTAG tag; + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + if (IsFreeAdvisoryLockInner(lockname)) + PG_RETURN_NULL(); + uint32 lockid[4] = {0}; + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + bool res = LockRelease(&tag, ExclusiveLock, true); + if (res) { + SetLockNameHash(lockname, true); + PG_RETURN_INT32(1); + } + else + PG_RETURN_INT32(0); +} + +Datum IsFreeAdvisoryLock(PG_FUNCTION_ARGS) +{ + char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + if (IsFreeAdvisoryLockInner(lockname)) + PG_RETURN_INT32(1); + PG_RETURN_INT32(0); +} + +Datum IsUsedAdvisoryLock(PG_FUNCTION_ARGS) +{ + char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + LOCKTAG tag; + uint32 lockid[4] = {0}; + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + uint32 hashcode = LockTagHashCode(&tag); + LWLock *partitionLock = NULL; + partitionLock = LockHashPartitionLock(hashcode); + LWLockAcquire(partitionLock, LW_SHARED); + bool foundCachedEntry = false; + LOCK *lock = + (LOCK *)hash_search_with_hash_value(t_thrd.storage_cxt.LockMethodLockHash, &tag, hashcode, HASH_FIND, &foundCachedEntry); + + if (!foundCachedEntry) { + LWLockRelease(partitionLock); + PG_RETURN_NULL(); + } + PROCLOCK *proclock; + SHM_QUEUE *procLocks; + PGPROC *proc; + uint64 sessionId = 0; + procLocks = &(lock->procLocks); + proclock = (PROCLOCK *)SHMQueueNext(procLocks, procLocks, offsetof(PROCLOCK, lockLink)); + while (proclock != NULL) { + proc = proclock->tag.myProc; + sessionId = proc->sessionid; + proclock = (PROCLOCK *)SHMQueueNext(procLocks, &proclock->lockLink, offsetof(PROCLOCK, lockLink)); + break; + } + LWLockRelease(partitionLock); + if (sessionId == 0) + PG_RETURN_NULL(); + return UInt64GetDatum(sessionId); +} + +Datum ReleaseAllAdvisoryLock(PG_FUNCTION_ARGS) +{ + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + LockReleaseSession(USER_LOCKMETHOD); + auto ret = LockNameHashRelease(); + PG_RETURN_INT64(ret); +} + +Datum GetAllAdvisoryLock(PG_FUNCTION_ARGS) +{ + FuncCallContext* funcctx = NULL; + HASH_SEQ_STATUS* status = NULL; + if (SRF_IS_FIRSTCALL()) { + TupleDesc tupdesc; + MemoryContext oldcontext; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* build tupdesc for result tuples */ + tupdesc = CreateTemplateTupleDesc(2, false, TAM_HEAP); + TupleDescInitEntry(tupdesc, (AttrNumber)1, "lockname", TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber)2, "sessionid", INT8OID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + status = (HASH_SEQ_STATUS*)palloc0(sizeof(HASH_SEQ_STATUS)); + hash_seq_init(status, lockNameHash); + funcctx->user_fctx = (void*)status; + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + LockName *lockName = NULL; + status = (HASH_SEQ_STATUS*)(funcctx->user_fctx); + + while ((lockName = (LockName *)hash_seq_search(status)) != NULL) { + Datum values[2]; + bool nulls[2]; + HeapTuple tuple; + Datum result; + /* + * Form tuple with appropriate data. + */ + errno_t rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "\0", "\0"); + rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls)); + securec_check(rc, "\0", "\0"); + values[0] = CStringGetTextDatum(lockName->name); + fcinfo->arg[0] = values[0]; + Datum datum1 = IsUsedAdvisoryLock(fcinfo); + if (datum1 == 0) { + fcinfo->isnull = false; + AutoMutexLock nameHashLock(&gNameHashLock); + nameHashLock.lock(); + hash_search(lockNameHash, (void *)&(lockName->name), HASH_REMOVE, NULL); + nameHashLock.unLock(); + continue; + } + values[1] = datum1; + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } + + SRF_RETURN_DONE(funcctx); +} + #ifdef PGXC /* * pgxc_lock_for_backup diff --git a/contrib/b_sql_plugin/sql/mysqllock_test.7z b/contrib/b_sql_plugin/sql/mysqllock_test.7z new file mode 100644 index 0000000000000000000000000000000000000000..d35fbd68e51bc2647944467b3f0fbd940660ef19 GIT binary patch literal 31449 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 Date: Tue, 10 May 2022 13:46:44 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=85=BC=E5=AE=B9mysql=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E9=94=81=EF=BC=8C=E6=B8=85=E7=A9=BAlockname=20hash=E8=A1=A8?= =?UTF-8?q?=E4=B8=AD=E6=97=A0=E6=95=88=E7=9A=84=E9=94=81=EF=BC=8C=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E9=83=A8=E5=88=86comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../b_sql_plugin/include/plugin_postgres.h | 3 +- contrib/b_sql_plugin/plugin_postgres.cpp | 1 + .../plugin_utils/adt/lockfuncs.cpp | 348 ++++++++++-------- contrib/b_sql_plugin/sql_script/lockfuncs.sql | 4 + 4 files changed, 204 insertions(+), 152 deletions(-) diff --git a/contrib/b_sql_plugin/include/plugin_postgres.h b/contrib/b_sql_plugin/include/plugin_postgres.h index 62aa9df0e..026a395ba 100644 --- a/contrib/b_sql_plugin/include/plugin_postgres.h +++ b/contrib/b_sql_plugin/include/plugin_postgres.h @@ -58,8 +58,9 @@ typedef struct BSqlPluginContext { bool enableBFormatMode; char* sqlModeString; unsigned int sqlModeFlags; + List* lockNameList; } bSqlPluginContext; BSqlPluginContext* GetSessionContext(); -#endif \ No newline at end of file +#endif diff --git a/contrib/b_sql_plugin/plugin_postgres.cpp b/contrib/b_sql_plugin/plugin_postgres.cpp index 0e1352e28..69d60e896 100644 --- a/contrib/b_sql_plugin/plugin_postgres.cpp +++ b/contrib/b_sql_plugin/plugin_postgres.cpp @@ -7047,6 +7047,7 @@ void init_session_vars(void) BSqlPluginContext *cxt = (BSqlPluginContext *) MemoryContextAlloc(u_sess->self_mem_cxt, sizeof(bSqlPluginContext)); u_sess->attr.attr_common.extension_session_vars_array[b_sql_plugin_index] = cxt; cxt->enableBFormatMode = false; + cxt->lockNameList = NIL; DefineCustomBoolVariable("b_format_mode", "Enable mysql functions override opengauss's when collision happens.", diff --git a/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp b/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp index ad1817bcd..fd7de91c6 100644 --- a/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp +++ b/contrib/b_sql_plugin/plugin_utils/adt/lockfuncs.cpp @@ -70,20 +70,107 @@ typedef struct LockName { char reserved[63]; } LockName; +PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLockWithtimeTextFormat); +extern "C" DLL_PUBLIC Datum GetAdvisoryLockWithtimeTextFormat(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLockWithtime); +extern "C" DLL_PUBLIC Datum GetAdvisoryLockWithtime(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLock); +extern "C" DLL_PUBLIC Datum GetAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(ReleaseAdvisoryLock); +extern "C" DLL_PUBLIC Datum ReleaseAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(IsFreeAdvisoryLock); +extern "C" DLL_PUBLIC Datum IsFreeAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(IsUsedAdvisoryLock); +extern "C" DLL_PUBLIC Datum IsUsedAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(ReleaseAllAdvisoryLock); +extern "C" DLL_PUBLIC Datum ReleaseAllAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(GetAllAdvisoryLock); +extern "C" DLL_PUBLIC Datum GetAllAdvisoryLock(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1_PUBLIC(ClearInvalidLockName); +extern "C" DLL_PUBLIC Datum ClearInvalidLockName(PG_FUNCTION_ARGS); + void InitLockNameHash() { - int hashFlags; HASHCTL info = {0}; info.keysize = MAX_ADVISORY_LOCK_NAME_LENTH+1; info.entrysize = sizeof(LockName); info.hash = string_hash; info.hcxt = g_instance.builtin_proc_context; - hashFlags = (HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); - lockNameHash = hash_create("Lock Name Hash", 100, &info, hashFlags); + lockNameHash = hash_create("Lock Name Hash", 100, &info, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); +} + +uint32 *AdvisoryLockGetIdFromName(char *name, uint32 *lockid) +{ + uint8 sum[16]; + if (strlen(name) > MAX_ADVISORY_LOCK_NAME_LENTH) { + ereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG), errmsg("max length of advisory lock is 64"))); + } + if (strlen(name) == 0) { + ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("Incorrect user-level lock name '%s'", name))); + } + + pg_md5_binary(name, strlen(name), sum); + + // merge uint8 sum[16] into uint32 lockid[4] + for (int i = 0; i < 4; i++) { + int index = i * 4; + for (int j = 0; j < 4; j++) { + uint32 t = sum[index + j]; + lockid[i] = lockid[i] << 8; + lockid[i] |= t; + } + } + return lockid; +} + +bool IsFreeAdvisoryLockInner(char *lockname) +{ + LOCKTAG tag; + uint32 lockid[4] = {0}; + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + uint32 hashcode = LockTagHashCode(&tag); + LWLock *partitionLock = NULL; + partitionLock = LockHashPartitionLock(hashcode); + LWLockAcquire(partitionLock, LW_SHARED); + bool foundCachedEntry = false; + hash_search_with_hash_value(t_thrd.storage_cxt.LockMethodLockHash, &tag, hashcode, HASH_FIND, &foundCachedEntry); + LWLockRelease(partitionLock); + return !foundCachedEntry; +} + +Datum ClearInvalidLockNameInner() +{ + HASH_SEQ_STATUS status; + LockName *lockName = NULL; + int64 sumLocks = 0; + + AutoMutexLock nameHashLock(&gNameHashLock); + nameHashLock.lock(); + hash_seq_init(&status, lockNameHash); + + while ((lockName = (LockName *)hash_seq_search(&status)) != NULL) { + if (IsFreeAdvisoryLockInner(lockName->name)) { + sumLocks += lockName->nlocks; + lockName->nlocks = 0; + hash_search(lockNameHash, (void *)&(lockName->name), HASH_REMOVE, NULL); + } + } + nameHashLock.unLock(); + + PG_RETURN_INT64(sumLocks); } -static void SetLockNameHash(const char* name, bool releaseFlag = false) +void SetLockNameHash(const char* name, bool nlockInitialFlag = false, bool releaseFlag = false) { char tempName[MAX_ADVISORY_LOCK_NAME_LENTH+1] = {0}; int rc = strncpy_s((char*)tempName, MAX_ADVISORY_LOCK_NAME_LENTH+1, name, strlen(name)); @@ -101,7 +188,10 @@ static void SetLockNameHash(const char* name, bool releaseFlag = false) if (result->nlocks == 0) hash_search(lockNameHash, (void *)&(result->name), HASH_REMOVE, NULL); } else if (!releaseFlag) { - if (found) + auto oldcxt = MemoryContextSwitchTo(u_sess->self_mem_cxt); + GetSessionContext()->lockNameList = lappend(GetSessionContext()->lockNameList, result); + MemoryContextSwitchTo(oldcxt); + if (found && !nlockInitialFlag) result->nlocks++; else result->nlocks = 1; @@ -109,17 +199,99 @@ static void SetLockNameHash(const char* name, bool releaseFlag = false) nameHashLock.unLock(); } -static int64 LockNameHashRelease() +Datum IsUsedAdvisoryLockInner(char *lockname) +{ + LOCKTAG tag; + uint32 lockid[4] = {0}; + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + uint32 hashcode = LockTagHashCode(&tag); + LWLock *partitionLock = NULL; + partitionLock = LockHashPartitionLock(hashcode); + LWLockAcquire(partitionLock, LW_SHARED); + bool foundCachedEntry = false; + LOCK *lock = + (LOCK *)hash_search_with_hash_value(t_thrd.storage_cxt.LockMethodLockHash, &tag, hashcode, HASH_FIND, &foundCachedEntry); + + if (!foundCachedEntry) { + LWLockRelease(partitionLock); + return (Datum)0; + } + PROCLOCK *proclock; + SHM_QUEUE *procLocks; + PGPROC *proc; + uint64 sessionId = 0; + procLocks = &(lock->procLocks); + proclock = (PROCLOCK *)SHMQueueNext(procLocks, procLocks, offsetof(PROCLOCK, lockLink)); + while (proclock != NULL) { + proc = proclock->tag.myProc; + sessionId = proc->sessionid; + proclock = (PROCLOCK *)SHMQueueNext(procLocks, &proclock->lockLink, offsetof(PROCLOCK, lockLink)); + break; + } + LWLockRelease(partitionLock); + if (sessionId == 0) + return (Datum)0; + return UInt64GetDatum(sessionId); +} + +int GetAdvisoryLockWithTimeInner(char *lockname, double timeout) +{ + + TimestampTz startTime; + LOCKTAG tag; + uint32 lockid[4] = {0}; + uint64 currentSessionId = IS_THREAD_POOL_WORKER ? u_sess->session_id : t_thrd.proc_cxt.MyProcPid; + if (g_instance.attr.attr_common.enable_thread_pool) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); + } + AdvisoryLockGetIdFromName(lockname, lockid); + SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + if (t_thrd.shemem_ptr_cxt.mySessionTimeEntry) { + startTime = GetCurrentTimestamp(); + } else { + ereport(WARNING, (errmsg("TimeEntry not inited"))); + return -1; + } + ClearInvalidLockNameInner(); + bool nlockInitialFlag = (DatumGetUInt64(IsUsedAdvisoryLockInner(lockname)) != currentSessionId); + if (timeout <= 0) { + LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, false, false); + if (res == LOCKACQUIRE_NOT_AVAIL) { + return 0; + } else { + SetLockNameHash(lockname, nlockInitialFlag); + return 1; + } + } else { + do { + LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, true, false); + if (res == LOCKACQUIRE_NOT_AVAIL) { + pg_usleep(USECS_PER_MSEC); + ProcessInterrupts(); + } else { + SetLockNameHash(lockname, nlockInitialFlag); + return 1; + } + } while (ComputeTimeStamp(startTime) / MSECS_PER_SEC < timeout); + } + return 0; +} + +int64 LockNameHashRelease() { - HASH_SEQ_STATUS status; LockName *lockName = NULL; int64 sumLocks = 0; + ListCell* lc = NULL; AutoMutexLock nameHashLock(&gNameHashLock); nameHashLock.lock(); - hash_seq_init(&status, lockNameHash); - while ((lockName = (LockName *)hash_seq_search(&status)) != NULL) { + foreach (lc, GetSessionContext()->lockNameList) { + lockName = (LockName*)lfirst(lc); sumLocks += lockName->nlocks; lockName->nlocks = 0; hash_search(lockNameHash, (void *)&(lockName->name), HASH_REMOVE, NULL); @@ -1374,100 +1546,11 @@ Datum pg_advisory_unlock_all(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } - -PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLockWithtimeTextFormat); -extern "C" DLL_PUBLIC Datum GetAdvisoryLockWithtimeTextFormat(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLockWithtime); -extern "C" DLL_PUBLIC Datum GetAdvisoryLockWithtime(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1_PUBLIC(GetAdvisoryLock); -extern "C" DLL_PUBLIC Datum GetAdvisoryLock(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1_PUBLIC(ReleaseAdvisoryLock); -extern "C" DLL_PUBLIC Datum ReleaseAdvisoryLock(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1_PUBLIC(IsFreeAdvisoryLock); -extern "C" DLL_PUBLIC Datum IsFreeAdvisoryLock(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1_PUBLIC(IsUsedAdvisoryLock); -extern "C" DLL_PUBLIC Datum IsUsedAdvisoryLock(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1_PUBLIC(ReleaseAllAdvisoryLock); -extern "C" DLL_PUBLIC Datum ReleaseAllAdvisoryLock(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1_PUBLIC(GetAllAdvisoryLock); -extern "C" DLL_PUBLIC Datum GetAllAdvisoryLock(PG_FUNCTION_ARGS); - -uint32 *AdvisoryLockGetIdFromName(char *name, uint32 *lockid) -{ - uint8 sum[16]; - if (strlen(name) > MAX_ADVISORY_LOCK_NAME_LENTH) { - ereport(ERROR, (errcode(ERRCODE_NAME_TOO_LONG), errmsg("max length of advisory lock is 64"))); - } - if (strlen(name) == 0) { - ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("Incorrect user-level lock name '%s'", name))); - } - - pg_md5_binary(name, strlen(name), sum); - - // merge uint8 sum[16] into uint32 lockid[4] - for (int i = 0; i < 4; i++) { - int index = i * 4; - for (int j = 0; j < 4; j++) { - uint32 t = sum[index + j]; - lockid[i] = lockid[i] << 8; - lockid[i] |= t; - } - } - return lockid; -} - -int GetAdvisoryLockWithTimeInner(char *lockname, double timeout) -{ - - TimestampTz startTime; - LOCKTAG tag; - uint32 lockid[4] = {0}; - if (g_instance.attr.attr_common.enable_thread_pool) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); - } - AdvisoryLockGetIdFromName(lockname, lockid); - SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); - if (t_thrd.shemem_ptr_cxt.mySessionTimeEntry) { - startTime = GetCurrentTimestamp(); - } else { - ereport(WARNING, (errmsg("TimeEntry not inited"))); - return -1; - } - if (timeout <= 0) { - LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, false, false); - if (res == LOCKACQUIRE_NOT_AVAIL) { - return 0; - } else { - SetLockNameHash(lockname); - return 1; - } - } else { - do { - LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, true, false); - if (res == LOCKACQUIRE_NOT_AVAIL) { - pg_usleep(USECS_PER_MSEC); - ProcessInterrupts(); - } else { - SetLockNameHash(lockname); - return 1; - } - } while (ComputeTimeStamp(startTime) / MSECS_PER_SEC < timeout); - } - return 0; -} - Datum GetAdvisoryLockWithtimeTextFormat(PG_FUNCTION_ARGS) { char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *timeoutStr = text_to_cstring(PG_GETARG_TEXT_PP(1)); - char *ptr; + char *ptr = NULL; double timeout = strtod(timeoutStr, &ptr); if (strlen(ptr) > 0) { ereport(WARNING, (errmsg("truncated incorrect value: %s", ptr))); @@ -1495,37 +1578,24 @@ Datum GetAdvisoryLock(PG_FUNCTION_ARGS) char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); LOCKTAG tag; uint32 lockid[4] = {0}; + uint64 currentSessionId = IS_THREAD_POOL_WORKER ? u_sess->session_id : t_thrd.proc_cxt.MyProcPid; if (g_instance.attr.attr_common.enable_thread_pool) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); } AdvisoryLockGetIdFromName(lockname, lockid); SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); + ClearInvalidLockNameInner(); + bool nlockInitialFlag = (DatumGetUInt64(IsUsedAdvisoryLockInner(lockname)) != currentSessionId); LockAcquireResult res = LockAcquire(&tag, ExclusiveLock, true, false, false); if (res == LOCKACQUIRE_NOT_AVAIL) { PG_RETURN_INT32(0); } else { - SetLockNameHash(lockname); + SetLockNameHash(lockname, nlockInitialFlag); PG_RETURN_INT32(1); } } -bool IsFreeAdvisoryLockInner(char *lockname) -{ - LOCKTAG tag; - uint32 lockid[4] = {0}; - AdvisoryLockGetIdFromName(lockname, lockid); - SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); - uint32 hashcode = LockTagHashCode(&tag); - LWLock *partitionLock = NULL; - partitionLock = LockHashPartitionLock(hashcode); - LWLockAcquire(partitionLock, LW_SHARED); - bool foundCachedEntry = false; - hash_search_with_hash_value(t_thrd.storage_cxt.LockMethodLockHash, &tag, hashcode, HASH_FIND, &foundCachedEntry); - LWLockRelease(partitionLock); - return !foundCachedEntry; -} - Datum ReleaseAdvisoryLock(PG_FUNCTION_ARGS) { char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); @@ -1540,7 +1610,7 @@ Datum ReleaseAdvisoryLock(PG_FUNCTION_ARGS) SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); bool res = LockRelease(&tag, ExclusiveLock, true); if (res) { - SetLockNameHash(lockname, true); + SetLockNameHash(lockname, false, true); PG_RETURN_INT32(1); } else @@ -1561,41 +1631,11 @@ Datum IsFreeAdvisoryLock(PG_FUNCTION_ARGS) Datum IsUsedAdvisoryLock(PG_FUNCTION_ARGS) { char *lockname = text_to_cstring(PG_GETARG_TEXT_PP(0)); - LOCKTAG tag; - uint32 lockid[4] = {0}; - if (g_instance.attr.attr_common.enable_thread_pool) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); - } - AdvisoryLockGetIdFromName(lockname, lockid); - SET_LOCKTAG_ADVISORY_5(tag, u_sess->proc_cxt.MyDatabaseId, lockid[0], lockid[1], lockid[2], lockid[3]); - uint32 hashcode = LockTagHashCode(&tag); - LWLock *partitionLock = NULL; - partitionLock = LockHashPartitionLock(hashcode); - LWLockAcquire(partitionLock, LW_SHARED); - bool foundCachedEntry = false; - LOCK *lock = - (LOCK *)hash_search_with_hash_value(t_thrd.storage_cxt.LockMethodLockHash, &tag, hashcode, HASH_FIND, &foundCachedEntry); - - if (!foundCachedEntry) { - LWLockRelease(partitionLock); + Datum sessionid = IsUsedAdvisoryLockInner(lockname); + if (sessionid == 0) PG_RETURN_NULL(); - } - PROCLOCK *proclock; - SHM_QUEUE *procLocks; - PGPROC *proc; - uint64 sessionId = 0; - procLocks = &(lock->procLocks); - proclock = (PROCLOCK *)SHMQueueNext(procLocks, procLocks, offsetof(PROCLOCK, lockLink)); - while (proclock != NULL) { - proc = proclock->tag.myProc; - sessionId = proc->sessionid; - proclock = (PROCLOCK *)SHMQueueNext(procLocks, &proclock->lockLink, offsetof(PROCLOCK, lockLink)); - break; - } - LWLockRelease(partitionLock); - if (sessionId == 0) - PG_RETURN_NULL(); - return UInt64GetDatum(sessionId); + else + return sessionid; } Datum ReleaseAllAdvisoryLock(PG_FUNCTION_ARGS) @@ -1603,6 +1643,7 @@ Datum ReleaseAllAdvisoryLock(PG_FUNCTION_ARGS) if (g_instance.attr.attr_common.enable_thread_pool) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport using advisory lock in threadpool"))); } + ClearInvalidLockNameInner(); LockReleaseSession(USER_LOCKMETHOD); auto ret = LockNameHashRelease(); PG_RETURN_INT64(ret); @@ -1671,6 +1712,11 @@ Datum GetAllAdvisoryLock(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } +Datum ClearInvalidLockName(PG_FUNCTION_ARGS) +{ + return ClearInvalidLockNameInner(); +} + #ifdef PGXC /* * pgxc_lock_for_backup diff --git a/contrib/b_sql_plugin/sql_script/lockfuncs.sql b/contrib/b_sql_plugin/sql_script/lockfuncs.sql index b3f850335..f57524a73 100644 --- a/contrib/b_sql_plugin/sql_script/lockfuncs.sql +++ b/contrib/b_sql_plugin/sql_script/lockfuncs.sql @@ -6,6 +6,7 @@ DROP FUNCTION IF EXISTS pg_catalog.is_free_lock(text) CASCADE; DROP FUNCTION IF EXISTS pg_catalog.is_used_lock(text) CASCADE; DROP FUNCTION IF EXISTS pg_catalog.release_all_locks() CASCADE; DROP FUNCTION IF EXISTS pg_catalog.get_all_locks() CASCADE; +DROP FUNCTION IF EXISTS pg_catalog.clear_all_invalid_locks() CASCADE; CREATE FUNCTION pg_catalog.get_lock ( text, @@ -40,3 +41,6 @@ CREATE FUNCTION pg_catalog.get_all_locks ( lockname out text, sessionid out bigint ) RETURNS setof record LANGUAGE C STRICT as '$libdir/b_sql_plugin', 'GetAllAdvisoryLock'; + +CREATE FUNCTION pg_catalog.clear_all_invalid_locks ( +) RETURNS bigint LANGUAGE C IMMUTABLE STRICT as '$libdir/b_sql_plugin', 'ClearInvalidLockName'; -- Gitee From 034a023ea90e0c98ed320e64e8b326ceea7d0218 Mon Sep 17 00:00:00 2001 From: WangXiuqiang Date: Fri, 20 May 2022 14:13:17 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9lock=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contrib/dolphin/sql/mysqllock_test.7z | Bin 31449 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 contrib/dolphin/sql/mysqllock_test.7z diff --git a/contrib/dolphin/sql/mysqllock_test.7z b/contrib/dolphin/sql/mysqllock_test.7z deleted file mode 100644 index d35fbd68e51bc2647944467b3f0fbd940660ef19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31449 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