From 270ce4cf0dff364ac68ab4f951ca3b2bc4f25068 Mon Sep 17 00:00:00 2001 From: Mark Dilger Date: Wed, 21 Oct 2020 20:26:07 -0700 Subject: [PATCH v20 3/5] Creating non-throwing interface to clog and slru. --- src/backend/access/transam/clog.c | 21 +++--- src/backend/access/transam/commit_ts.c | 4 +- src/backend/access/transam/multixact.c | 16 ++--- src/backend/access/transam/slru.c | 23 +++--- src/backend/access/transam/subtrans.c | 4 +- src/backend/access/transam/transam.c | 98 +++++++++----------------- src/backend/commands/async.c | 4 +- src/backend/storage/lmgr/predicate.c | 4 +- src/include/access/clog.h | 18 +---- src/include/access/clogdefs.h | 33 +++++++++ src/include/access/slru.h | 6 +- src/include/access/transam.h | 3 + 12 files changed, 122 insertions(+), 112 deletions(-) create mode 100644 src/include/access/clogdefs.h diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index 034349aa7b..a2eb3e2983 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -357,7 +357,7 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids, * write-busy, since we don't care if the update reaches disk sooner than * we think. */ - slotno = SimpleLruReadPage(XactCtl, pageno, XLogRecPtrIsInvalid(lsn), xid); + slotno = SimpleLruReadPage(XactCtl, pageno, XLogRecPtrIsInvalid(lsn), xid, true); /* * Set the main transaction id, if any. @@ -631,7 +631,7 @@ TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, i * for most uses; TransactionLogFetch() in transam.c is the intended caller. */ XidStatus -TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn) +TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn, bool throwError) { int pageno = TransactionIdToPage(xid); int byteno = TransactionIdToByte(xid); @@ -643,13 +643,18 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn) /* lock is acquired by SimpleLruReadPage_ReadOnly */ - slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno, xid); - byteptr = XactCtl->shared->page_buffer[slotno] + byteno; + slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno, xid, throwError); + if (slotno == InvalidSlotNo) + status = TRANSACTION_STATUS_UNKNOWN; + else + { + byteptr = XactCtl->shared->page_buffer[slotno] + byteno; - status = (*byteptr >> bshift) & CLOG_XACT_BITMASK; + status = (*byteptr >> bshift) & CLOG_XACT_BITMASK; - lsnindex = GetLSNIndex(slotno, xid); - *lsn = XactCtl->shared->group_lsn[lsnindex]; + lsnindex = GetLSNIndex(slotno, xid); + *lsn = XactCtl->shared->group_lsn[lsnindex]; + } LWLockRelease(XactSLRULock); @@ -796,7 +801,7 @@ TrimCLOG(void) int slotno; char *byteptr; - slotno = SimpleLruReadPage(XactCtl, pageno, false, xid); + slotno = SimpleLruReadPage(XactCtl, pageno, false, xid, true); byteptr = XactCtl->shared->page_buffer[slotno] + byteno; /* Zero so-far-unused positions in the current byte */ diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c index cb8a968801..98c685405c 100644 --- a/src/backend/access/transam/commit_ts.c +++ b/src/backend/access/transam/commit_ts.c @@ -237,7 +237,7 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids, LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE); - slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, xid); + slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, xid, true); TransactionIdSetCommitTs(xid, ts, nodeid, slotno); for (i = 0; i < nsubxids; i++) @@ -342,7 +342,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts, } /* lock is acquired by SimpleLruReadPage_ReadOnly */ - slotno = SimpleLruReadPage_ReadOnly(CommitTsCtl, pageno, xid); + slotno = SimpleLruReadPage_ReadOnly(CommitTsCtl, pageno, xid, true); memcpy(&entry, CommitTsCtl->shared->page_buffer[slotno] + SizeOfCommitTimestampEntry * entryno, diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 43653fe572..ed902a9d64 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -881,7 +881,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset, * enough that a MultiXactId is really involved. Perhaps someday we'll * take the trouble to generalize the slru.c error reporting code. */ - slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi); + slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi, true); offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno]; offptr += entryno; @@ -914,7 +914,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset, if (pageno != prev_pageno) { - slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi); + slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi, true); prev_pageno = pageno; } @@ -1345,7 +1345,7 @@ retry: pageno = MultiXactIdToOffsetPage(multi); entryno = MultiXactIdToOffsetEntry(multi); - slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi); + slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi, true); offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno]; offptr += entryno; offset = *offptr; @@ -1377,7 +1377,7 @@ retry: entryno = MultiXactIdToOffsetEntry(tmpMXact); if (pageno != prev_pageno) - slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, tmpMXact); + slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, tmpMXact, true); offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno]; offptr += entryno; @@ -1418,7 +1418,7 @@ retry: if (pageno != prev_pageno) { - slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi); + slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi, true); prev_pageno = pageno; } @@ -2063,7 +2063,7 @@ TrimMultiXact(void) int slotno; MultiXactOffset *offptr; - slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact); + slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact, true); offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno]; offptr += entryno; @@ -2095,7 +2095,7 @@ TrimMultiXact(void) int memberoff; memberoff = MXOffsetToMemberOffset(offset); - slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, offset); + slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, offset, true); xidptr = (TransactionId *) (MultiXactMemberCtl->shared->page_buffer[slotno] + memberoff); @@ -2749,7 +2749,7 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result) return false; /* lock is acquired by SimpleLruReadPage_ReadOnly */ - slotno = SimpleLruReadPage_ReadOnly(MultiXactOffsetCtl, pageno, multi); + slotno = SimpleLruReadPage_ReadOnly(MultiXactOffsetCtl, pageno, multi, true); offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno]; offptr += entryno; offset = *offptr; diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c index 16a7898697..daa145eeff 100644 --- a/src/backend/access/transam/slru.c +++ b/src/backend/access/transam/slru.c @@ -385,14 +385,15 @@ SimpleLruWaitIO(SlruCtl ctl, int slotno) * The passed-in xid is used only for error reporting, and may be * InvalidTransactionId if no specific xid is associated with the action. * - * Return value is the shared-buffer slot number now holding the page. - * The buffer's LRU access info is updated. + * On error, when throwError is false, the return value is negative. + * Otherwise, return value is the shared-buffer slot number now holding the + * page, and the buffer's LRU access info is updated. * * Control lock must be held at entry, and will be held at exit. */ int SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok, - TransactionId xid) + TransactionId xid, bool throwError) { SlruShared shared = ctl->shared; @@ -465,7 +466,11 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok, /* Now it's okay to ereport if we failed */ if (!ok) - SlruReportIOError(ctl, pageno, xid); + { + if (throwError) + SlruReportIOError(ctl, pageno, xid); + return InvalidSlotNo; + } SlruRecentlyUsed(shared, slotno); @@ -484,14 +489,16 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok, * The passed-in xid is used only for error reporting, and may be * InvalidTransactionId if no specific xid is associated with the action. * - * Return value is the shared-buffer slot number now holding the page. - * The buffer's LRU access info is updated. + * On error, when throwError is false, the return value is negative. + * Otherwise, return value is the shared-buffer slot number now holding the + * page, and the buffer's LRU access info is updated. * * Control lock must NOT be held at entry, but will be held at exit. * It is unspecified whether the lock will be shared or exclusive. */ int -SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid) +SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid, + bool throwError) { SlruShared shared = ctl->shared; int slotno; @@ -520,7 +527,7 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid) LWLockRelease(shared->ControlLock); LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE); - return SimpleLruReadPage(ctl, pageno, true, xid); + return SimpleLruReadPage(ctl, pageno, true, xid, throwError); } /* diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c index 0111e867c7..353b946731 100644 --- a/src/backend/access/transam/subtrans.c +++ b/src/backend/access/transam/subtrans.c @@ -83,7 +83,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent) LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE); - slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid); + slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid, true); ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno]; ptr += entryno; @@ -123,7 +123,7 @@ SubTransGetParent(TransactionId xid) /* lock is acquired by SimpleLruReadPage_ReadOnly */ - slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid); + slotno = SimpleLruReadPage_ReadOnly(SubTransCtl, pageno, xid, true); ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno]; ptr += entryno; diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c index a28918657c..88f867e5ef 100644 --- a/src/backend/access/transam/transam.c +++ b/src/backend/access/transam/transam.c @@ -35,7 +35,8 @@ static XidStatus cachedFetchXidStatus; static XLogRecPtr cachedCommitLSN; /* Local functions */ -static XidStatus TransactionLogFetch(TransactionId transactionId); +static XidStatus TransactionLogFetch(TransactionId transactionId, + bool throwError); /* ---------------------------------------------------------------- @@ -49,7 +50,7 @@ static XidStatus TransactionLogFetch(TransactionId transactionId); * TransactionLogFetch --- fetch commit status of specified transaction id */ static XidStatus -TransactionLogFetch(TransactionId transactionId) +TransactionLogFetch(TransactionId transactionId, bool throwError) { XidStatus xidstatus; XLogRecPtr xidlsn; @@ -76,14 +77,16 @@ TransactionLogFetch(TransactionId transactionId) /* * Get the transaction status. */ - xidstatus = TransactionIdGetStatus(transactionId, &xidlsn); + xidstatus = TransactionIdGetStatus(transactionId, &xidlsn, throwError); /* * Cache it, but DO NOT cache status for unfinished or sub-committed * transactions! We only cache status that is guaranteed not to change. + * Likewise, DO NOT cache when the status is unknown. */ if (xidstatus != TRANSACTION_STATUS_IN_PROGRESS && - xidstatus != TRANSACTION_STATUS_SUB_COMMITTED) + xidstatus != TRANSACTION_STATUS_SUB_COMMITTED && + xidstatus != TRANSACTION_STATUS_UNKNOWN) { cachedFetchXid = transactionId; cachedFetchXidStatus = xidstatus; @@ -96,6 +99,7 @@ TransactionLogFetch(TransactionId transactionId) /* ---------------------------------------------------------------- * Interface functions * + * TransactionIdResolveStatus * TransactionIdDidCommit * TransactionIdDidAbort * ======== @@ -115,24 +119,17 @@ TransactionLogFetch(TransactionId transactionId) */ /* - * TransactionIdDidCommit - * True iff transaction associated with the identifier did commit. - * - * Note: - * Assumes transaction identifier is valid and exists in clog. + * TransactionIdResolveStatus + * Returns the status of the transaction associated with the identifer, + * recursively resolving sub-committed transaction status by checking + * the parent transaction. */ -bool /* true if given transaction committed */ -TransactionIdDidCommit(TransactionId transactionId) +XidStatus +TransactionIdResolveStatus(TransactionId transactionId, bool throwError) { XidStatus xidstatus; - xidstatus = TransactionLogFetch(transactionId); - - /* - * If it's marked committed, it's committed. - */ - if (xidstatus == TRANSACTION_STATUS_COMMITTED) - return true; + xidstatus = TransactionLogFetch(transactionId, throwError); /* * If it's marked subcommitted, we have to check the parent recursively. @@ -153,21 +150,31 @@ TransactionIdDidCommit(TransactionId transactionId) TransactionId parentXid; if (TransactionIdPrecedes(transactionId, TransactionXmin)) - return false; + return TRANSACTION_STATUS_ABORTED; parentXid = SubTransGetParent(transactionId); if (!TransactionIdIsValid(parentXid)) { elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId); - return false; + return TRANSACTION_STATUS_ABORTED; } - return TransactionIdDidCommit(parentXid); + return TransactionIdResolveStatus(parentXid, throwError); } + return xidstatus; +} - /* - * It's not committed. - */ - return false; +/* + * TransactionIdDidCommit + * True iff transaction associated with the identifier did commit. + * + * Note: + * Assumes transaction identifier is valid and exists in clog. + */ +bool /* true if given transaction committed */ +TransactionIdDidCommit(TransactionId transactionId) +{ + return (TransactionIdResolveStatus(transactionId, true) == + TRANSACTION_STATUS_COMMITTED); } /* @@ -180,43 +187,8 @@ TransactionIdDidCommit(TransactionId transactionId) bool /* true if given transaction aborted */ TransactionIdDidAbort(TransactionId transactionId) { - XidStatus xidstatus; - - xidstatus = TransactionLogFetch(transactionId); - - /* - * If it's marked aborted, it's aborted. - */ - if (xidstatus == TRANSACTION_STATUS_ABORTED) - return true; - - /* - * If it's marked subcommitted, we have to check the parent recursively. - * However, if it's older than TransactionXmin, we can't look at - * pg_subtrans; instead assume that the parent crashed without cleaning up - * its children. - */ - if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED) - { - TransactionId parentXid; - - if (TransactionIdPrecedes(transactionId, TransactionXmin)) - return true; - parentXid = SubTransGetParent(transactionId); - if (!TransactionIdIsValid(parentXid)) - { - /* see notes in TransactionIdDidCommit */ - elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", - transactionId); - return true; - } - return TransactionIdDidAbort(parentXid); - } - - /* - * It's not aborted. - */ - return false; + return (TransactionIdResolveStatus(transactionId, true) == + TRANSACTION_STATUS_ABORTED); } /* @@ -419,7 +391,7 @@ TransactionIdGetCommitLSN(TransactionId xid) /* * Get the transaction status. */ - (void) TransactionIdGetStatus(xid, &result); + (void) TransactionIdGetStatus(xid, &result, true); return result; } diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index 8dbcace3f9..a49126dba0 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -1477,7 +1477,7 @@ asyncQueueAddEntries(ListCell *nextNotify) slotno = SimpleLruZeroPage(NotifyCtl, pageno); else slotno = SimpleLruReadPage(NotifyCtl, pageno, true, - InvalidTransactionId); + InvalidTransactionId, true); /* Note we mark the page dirty before writing in it */ NotifyCtl->shared->page_dirty[slotno] = true; @@ -2010,7 +2010,7 @@ asyncQueueReadAllNotifications(void) * part of the page we will actually inspect. */ slotno = SimpleLruReadPage_ReadOnly(NotifyCtl, curpage, - InvalidTransactionId); + InvalidTransactionId, true); if (curpage == QUEUE_POS_PAGE(head)) { /* we only want to read as far as head */ diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 8a365b400c..6cf12e46f6 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -904,7 +904,7 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo) slotno = SimpleLruZeroPage(SerialSlruCtl, targetPage); } else - slotno = SimpleLruReadPage(SerialSlruCtl, targetPage, true, xid); + slotno = SimpleLruReadPage(SerialSlruCtl, targetPage, true, xid, true); SerialValue(slotno, xid) = minConflictCommitSeqNo; SerialSlruCtl->shared->page_dirty[slotno] = true; @@ -946,7 +946,7 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid) * but will return with that lock held, which must then be released. */ slotno = SimpleLruReadPage_ReadOnly(SerialSlruCtl, - SerialPage(xid), xid); + SerialPage(xid), xid, true); val = SerialValue(slotno, xid); LWLockRelease(SerialSLRULock); return val; diff --git a/src/include/access/clog.h b/src/include/access/clog.h index 6c840cbf29..cf299cd8f6 100644 --- a/src/include/access/clog.h +++ b/src/include/access/clog.h @@ -11,24 +11,11 @@ #ifndef CLOG_H #define CLOG_H +#include "access/clogdefs.h" #include "access/xlogreader.h" #include "storage/sync.h" #include "lib/stringinfo.h" -/* - * Possible transaction statuses --- note that all-zeroes is the initial - * state. - * - * A "subcommitted" transaction is a committed subtransaction whose parent - * hasn't committed or aborted yet. - */ -typedef int XidStatus; - -#define TRANSACTION_STATUS_IN_PROGRESS 0x00 -#define TRANSACTION_STATUS_COMMITTED 0x01 -#define TRANSACTION_STATUS_ABORTED 0x02 -#define TRANSACTION_STATUS_SUB_COMMITTED 0x03 - typedef struct xl_clog_truncate { int pageno; @@ -38,7 +25,8 @@ typedef struct xl_clog_truncate extern void TransactionIdSetTreeStatus(TransactionId xid, int nsubxids, TransactionId *subxids, XidStatus status, XLogRecPtr lsn); -extern XidStatus TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn); +extern XidStatus TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn, + bool throwError); extern Size CLOGShmemBuffers(void); extern Size CLOGShmemSize(void); diff --git a/src/include/access/clogdefs.h b/src/include/access/clogdefs.h new file mode 100644 index 0000000000..0f9996bb08 --- /dev/null +++ b/src/include/access/clogdefs.h @@ -0,0 +1,33 @@ +/* + * clogdefs.h + * + * PostgreSQL transaction-commit-log manager + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/clogdefs.h + */ +#ifndef CLOGDEFS_H +#define CLOGDEFS_H + +/* + * Possible transaction statuses --- note that all-zeroes is the initial + * state. + * + * A "subcommitted" transaction is a committed subtransaction whose parent + * hasn't committed or aborted yet. + * + * An "unknown" status indicates an error condition, such as when the clog has + * been erroneously truncated and the commit status of a transaction cannot be + * determined. + */ +typedef enum XidStatus { + TRANSACTION_STATUS_IN_PROGRESS = 0x00, + TRANSACTION_STATUS_COMMITTED = 0x01, + TRANSACTION_STATUS_ABORTED = 0x02, + TRANSACTION_STATUS_SUB_COMMITTED = 0x03, + TRANSACTION_STATUS_UNKNOWN = 0x04 /* error condition */ +} XidStatus; + +#endif /* CLOG_H */ diff --git a/src/include/access/slru.h b/src/include/access/slru.h index b39b43504d..0b6a5669d8 100644 --- a/src/include/access/slru.h +++ b/src/include/access/slru.h @@ -133,6 +133,8 @@ typedef struct SlruCtlData typedef SlruCtlData *SlruCtl; +#define InvalidSlotNo ((int) -1) + extern Size SimpleLruShmemSize(int nslots, int nlsns); extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns, @@ -140,9 +142,9 @@ extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns, SyncRequestHandler sync_handler); extern int SimpleLruZeroPage(SlruCtl ctl, int pageno); extern int SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok, - TransactionId xid); + TransactionId xid, bool throwError); extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, - TransactionId xid); + TransactionId xid, bool throwError); extern void SimpleLruWritePage(SlruCtl ctl, int slotno); extern void SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied); extern void SimpleLruTruncate(SlruCtl ctl, int cutoffPage); diff --git a/src/include/access/transam.h b/src/include/access/transam.h index 2f1f144db4..7d5e2f614d 100644 --- a/src/include/access/transam.h +++ b/src/include/access/transam.h @@ -14,6 +14,7 @@ #ifndef TRANSAM_H #define TRANSAM_H +#include "access/clogdefs.h" #include "access/xlogdefs.h" @@ -264,6 +265,8 @@ extern PGDLLIMPORT VariableCache ShmemVariableCache; /* * prototypes for functions in transam/transam.c */ +extern XidStatus TransactionIdResolveStatus(TransactionId transactionId, + bool throwError); extern bool TransactionIdDidCommit(TransactionId transactionId); extern bool TransactionIdDidAbort(TransactionId transactionId); extern bool TransactionIdIsKnownCompleted(TransactionId transactionId); -- 2.21.1 (Apple Git-122.3)