Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mdQjZ-00069p-Vc for pgsql-hackers@arkaria.postgresql.org; Thu, 21 Oct 2021 05:35:22 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1mdQjY-0005Vw-FA for pgsql-hackers@arkaria.postgresql.org; Thu, 21 Oct 2021 05:35:20 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mdQjX-0005U0-Jd for pgsql-hackers@lists.postgresql.org; Thu, 21 Oct 2021 05:35:20 +0000 Received: from wout3-smtp.messagingengine.com ([64.147.123.19]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mdQjU-0007QM-DV for pgsql-hackers@lists.postgresql.org; Thu, 21 Oct 2021 05:35:19 +0000 Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailout.west.internal (Postfix) with ESMTP id 236C732009F8; Thu, 21 Oct 2021 01:35:14 -0400 (EDT) Received: from mailfrontend2 ([10.202.2.163]) by compute4.internal (MEProxy); Thu, 21 Oct 2021 01:35:14 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= date:from:to:cc:subject:message-id:references:mime-version :content-type:in-reply-to; s=fm3; bh=LQggliWuN5d9CaxW0Dqort5Frwb vAr55qRBkJpiQmgA=; b=k3BBv9w5Ge2aQR7iv3ogZ1NzbzjQmHLs3JwVUsxBEjm td77ex4N7ghMIulbpuWzCoV5X35r5D6FsBDtna5PvqxIhc8l/B53HHaEdGT/qlB8 KelEYjFO24wy22knUw0gwlfqcHlmpH0lW5q8ZHFtYwa3UVOxwFyE+LI+4KdlqeUu k/5NLSgB2JJhq9KVZe8YuccXw4hakUBSi3D7vPWZEwrivJSlI2a3ppnSTIK43ZYH jBdErvr9EhHJZq4HwXd0R1i1LSj53gpW9Ac5/t6MKQOmlo6RjaJOzdmJpAa0uAjW 581cvxDZ32vxtADGSFxh+NVvkyRLVD3iOMA9C3AHqIg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm1; bh=LQggli WuN5d9CaxW0Dqort5FrwbvAr55qRBkJpiQmgA=; b=LA7iZDJ39YHzt/zZ+NjLvH dc9PxfMI7PofJYRLmqYwaMpqhHySWK29YbbV9ndHOSzh6I74QEeDe+xkG4DuDfvE Lw1f3L6M9vURRy6mF0oCkEZgIdV7ubpY9NXIR4y80J/chxKbmmAS5u3/E/HZLoKb pd8bhz0s1jm76DlXju2VQ61UWqHiztbVhUO1pMD+zlov418vNog4XkKxW0l+sd38 l5HN5PUw4oabfYEv+OO9bS6LvGpBB17o47QU2+5KDt5gtfekJE58qQFLlwkkGzm8 9muf2UhO0VOeeM4+bu5D+WSBd1VFJxjhCd2Y+5mlTJjuWCMQhtAfBSoN2YsD6NBQ == X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvtddrvddvhedgleduucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne gfrhhlucfvnfffucdljedtmdenucfjughrpeffhffvuffkfhggtggujgesghdtreertddt vdenucfhrhhomhepofhitghhrggvlhcurfgrqhhuihgvrhcuoehmihgthhgrvghlsehprg hquhhivghrrdighiiiqeenucggtffrrghtthgvrhhnpedvgeduuefhtdeuleettdevjeeh heeiveeuieegleetgeeljeelieeuieehgeevhfenucevlhhushhtvghrufhiiigvpedtne curfgrrhgrmhepmhgrihhlfhhrohhmpehmihgthhgrvghlsehprghquhhivghrrdighiii X-ME-Proxy: Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu, 21 Oct 2021 01:35:11 -0400 (EDT) Date: Thu, 21 Oct 2021 14:35:08 +0900 From: Michael Paquier To: Ronan Dunklau Cc: Bharath Rupireddy , Kyotaro Horiguchi , PostgreSQL Hackers , Masahiko Sawada Subject: Re: pg_receivewal starting position Message-ID: References: <14470011.tv2OnDr8pf@aivenronan> <4755573.0VBMTVartN@aivenronan> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="mFeZEW/PmxDzb6Hu" Content-Disposition: inline In-Reply-To: <4755573.0VBMTVartN@aivenronan> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --mFeZEW/PmxDzb6Hu Content-Type: multipart/mixed; boundary="AOiPlOjeflOit5EO" Content-Disposition: inline --AOiPlOjeflOit5EO Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Wed, Oct 20, 2021 at 02:58:26PM +0200, Ronan Dunklau wrote: > After sending the previous patch suite, I figured it would be worthwhile = to=20 > also have tests covering timeline switches, which was not covered before. That seems independent to me. I'll take a look. > So please find attached a new version with an additional patch for those = tests,=20 > covering both "resume from last know archive" and "resume from the=20 > replication slots position" cases. So, taking things in order, I have looked at 0003 and 0001, and attached are refined versions for both of them. 0003 is an existing hole in the docs, which I think we had better address first and backpatch, taking into account that the starting point calculation considers compressed segments when looking for completed segments. Regarding 0001, I have found the last test to check for NULL values returned by READ_REPLICATION_SLOT after dropping the slot overlaps with the first test, so I have removed that. I have expanded a bit the use of like(), and there were some confusion with PostgresNode::psql and some extra arguments (see DROP_REPLICATION_SLOT and CREATE_REPLICATION_SLOT, and no need for return values in the=20 CREATE case either). Some comments, docs and code have been slightly tweaked. Here are some comments about 0002. + /* The commpand should always return precisely one tuple */ s/commpand/command/ + pg_log_error("could not fetch replication slot: got %d rows and %d = fields, expected %d rows and %d or more fields", + PQntuples(res), PQnfields(res), 1, 3); Should this be "could not read" instead? + if (sscanf(PQgetvalue(res, 0, 1), "%X/%X", &hi, &lo) !=3D 2) + { + pg_log_error("could not parse slot's restart_lsn \"%s\"", + PQgetvalue(res, 0, 1)); + PQclear(res); + return false; + } Wouldn't it be saner to initialize *restart_lsn and *restart_tli to some default values at the top of GetSlotInformation() instead, if they are specified by the caller? And I think that we should still complain even if restart_lsn is NULL. On a quick read of 0004, I find the split of the logic with change_timeline() a bit hard to understand. It looks like we should be able to make a cleaner split, but I am not sure how that would look, though. -- Michael --AOiPlOjeflOit5EO Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v9-0001-doc-Describe-calculation-method-of-streaming-star.patch" Content-Transfer-Encoding: quoted-printable =46rom afca5e895e07279049f272396e40bdb78ae61d0e Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 21 Oct 2021 13:21:43 +0900 Subject: [PATCH v9 1/2] doc: Describe calculation method of streaming start for pg_receivewal The documentation was unprecise about the fact that the current WAL flush location is used if nothing can be found on the local archive directory describe, independently of the compression used by each segment (ZLIB or uncompressed). --- doc/src/sgml/ref/pg_receivewal.sgml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/src/sgml/ref/pg_receivewal.sgml b/doc/src/sgml/ref/pg_rece= ivewal.sgml index 45b544cf49..9adbddb657 100644 --- a/doc/src/sgml/ref/pg_receivewal.sgml +++ b/doc/src/sgml/ref/pg_receivewal.sgml @@ -75,6 +75,29 @@ PostgreSQL documentation one session available for the stream. =20 + + The starting point of the write-ahead log streaming is calculated when + pg_receivewal starts: + + + + First, scan the directory where the WAL segment files are written and + find the newest completed segment, using as starting point the begin= ning + of the next WAL segment file. This is calculated independently of the + compression method used to compress each segment. + + + + + + If a starting point cannot be calculated with the previous method, + the latest WAL flush location is used as reported by the server from + a IDENTIFY_SYSTEM command. + + + + + If the connection is lost, or if it cannot be initially established, with a non-fatal error, pg_receivewal will --=20 2.33.0 --AOiPlOjeflOit5EO Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v9-0002-Add-READ_REPLICATION_SLOT.patch" Content-Transfer-Encoding: quoted-printable =46rom 3ef08cc14e4365eaec320294275bee9f19c7dbef Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 21 Oct 2021 14:23:41 +0900 Subject: [PATCH v9 2/2] Add READ_REPLICATION_SLOT --- src/include/nodes/nodes.h | 1 + src/include/nodes/replnodes.h | 11 +++ src/backend/replication/repl_gram.y | 16 +++- src/backend/replication/repl_scanner.l | 1 + src/backend/replication/walsender.c | 93 +++++++++++++++++++++ src/test/recovery/t/001_stream_rep.pl | 32 ++++++- src/test/recovery/t/006_logical_decoding.pl | 11 ++- doc/src/sgml/protocol.sgml | 48 +++++++++++ src/tools/pgindent/typedefs.list | 1 + 9 files changed, 211 insertions(+), 3 deletions(-) diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index e0057daa06..541e9861ba 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -496,6 +496,7 @@ typedef enum NodeTag T_BaseBackupCmd, T_CreateReplicationSlotCmd, T_DropReplicationSlotCmd, + T_ReadReplicationSlotCmd, T_StartReplicationCmd, T_TimeLineHistoryCmd, T_SQLCmd, diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h index faa3a251f2..a746fafc12 100644 --- a/src/include/nodes/replnodes.h +++ b/src/include/nodes/replnodes.h @@ -87,6 +87,17 @@ typedef struct StartReplicationCmd } StartReplicationCmd; =20 =20 +/* ---------------------- + * READ_REPLICATION_SLOT command + * ---------------------- + */ +typedef struct ReadReplicationSlotCmd +{ + NodeTag type; + char *slotname; +} ReadReplicationSlotCmd; + + /* ---------------------- * TIMELINE_HISTORY command * ---------------------- diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/= repl_gram.y index 126380e2df..dcb1108579 100644 --- a/src/backend/replication/repl_gram.y +++ b/src/backend/replication/repl_gram.y @@ -64,6 +64,7 @@ static SQLCmd *make_sqlcmd(void); /* Keyword tokens. */ %token K_BASE_BACKUP %token K_IDENTIFY_SYSTEM +%token K_READ_REPLICATION_SLOT %token K_SHOW %token K_START_REPLICATION %token K_CREATE_REPLICATION_SLOT @@ -94,7 +95,7 @@ static SQLCmd *make_sqlcmd(void); %type command %type base_backup start_replication start_logical_replication create_replication_slot drop_replication_slot identify_system - timeline_history show sql_cmd + read_replication_slot timeline_history show sql_cmd %type base_backup_legacy_opt_list generic_option_list %type base_backup_legacy_opt generic_option %type opt_timeline @@ -125,6 +126,7 @@ command: | start_logical_replication | create_replication_slot | drop_replication_slot + | read_replication_slot | timeline_history | show | sql_cmd @@ -140,6 +142,18 @@ identify_system: } ; =20 +/* + * READ_REPLICATION_SLOT %s + */ +read_replication_slot: + K_READ_REPLICATION_SLOT var_name + { + ReadReplicationSlotCmd *n =3D makeNode(ReadReplicationSlotCmd); + n->slotname =3D $2; + $$ =3D (Node *) n; + } + ; + /* * SHOW setting */ diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replicati= on/repl_scanner.l index c038a636c3..1b599c255e 100644 --- a/src/backend/replication/repl_scanner.l +++ b/src/backend/replication/repl_scanner.l @@ -85,6 +85,7 @@ identifier {ident_start}{ident_cont}* BASE_BACKUP { return K_BASE_BACKUP; } FAST { return K_FAST; } IDENTIFY_SYSTEM { return K_IDENTIFY_SYSTEM; } +READ_REPLICATION_SLOT { return K_READ_REPLICATION_SLOT; } SHOW { return K_SHOW; } LABEL { return K_LABEL; } NOWAIT { return K_NOWAIT; } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/= walsender.c index b811a5c0ef..7b3d16f731 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -232,6 +232,7 @@ static void XLogSendLogical(void); static void WalSndDone(WalSndSendDataCallback send_data); static XLogRecPtr GetStandbyFlushRecPtr(void); static void IdentifySystem(void); +static void ReadReplicationSlot(ReadReplicationSlotCmd *cmd); static void CreateReplicationSlot(CreateReplicationSlotCmd *cmd); static void DropReplicationSlot(DropReplicationSlotCmd *cmd); static void StartReplication(StartReplicationCmd *cmd); @@ -457,6 +458,91 @@ IdentifySystem(void) end_tup_output(tstate); } =20 +/* Handle READ_REPLICATION_SLOT command */ +static void +ReadReplicationSlot(ReadReplicationSlotCmd *cmd) +{ +#define READ_REPLICATION_SLOT_COLS 3 + ReplicationSlot *slot; + DestReceiver *dest; + TupOutputState *tstate; + TupleDesc tupdesc; + Datum values[READ_REPLICATION_SLOT_COLS]; + bool nulls[READ_REPLICATION_SLOT_COLS]; + + tupdesc =3D CreateTemplateTupleDesc(READ_REPLICATION_SLOT_COLS); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "slot_type", + TEXTOID, -1, 0); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "restart_lsn", + TEXTOID, -1, 0); + TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "restart_tli", + INT4OID, -1, 0); + + MemSet(nulls, true, READ_REPLICATION_SLOT_COLS * sizeof(bool)); + + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); + slot =3D SearchNamedReplicationSlot(cmd->slotname, false); + if (slot =3D=3D NULL || !slot->in_use) + { + LWLockRelease(ReplicationSlotControlLock); + } + else + { + ReplicationSlot slot_contents; + int i =3D 0; + + /* Copy slot contents while holding spinlock */ + SpinLockAcquire(&slot->mutex); + slot_contents =3D *slot; + SpinLockRelease(&slot->mutex); + LWLockRelease(ReplicationSlotControlLock); + + if (OidIsValid(slot_contents.data.database)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use \"%s\" with logical replication slots", + "READ_REPLICATION_SLOT")); + + /* slot type */ + values[i] =3D CStringGetTextDatum("physical"); + nulls[i] =3D false; + i++; + + /* start LSN */ + if (!XLogRecPtrIsInvalid(slot_contents.data.restart_lsn)) + { + char xloc[MAXFNAMELEN]; + + snprintf(xloc, sizeof(xloc), "%X/%X", + LSN_FORMAT_ARGS(slot_contents.data.restart_lsn)); + values[i] =3D CStringGetTextDatum(xloc); + nulls[i] =3D false; + } + i++; + + /* timeline this WAL was produced on */ + if (!XLogRecPtrIsInvalid(slot_contents.data.restart_lsn)) + { + TimeLineID slots_position_timeline; + List *timeline_history =3D NIL; + + timeline_history =3D readTimeLineHistory(ThisTimeLineID); + slots_position_timeline =3D tliOfPointInHistory(slot_contents.data.rest= art_lsn, + timeline_history); + values[i] =3D Int32GetDatum(slots_position_timeline); + nulls[i] =3D false; + } + i++; + + Assert(i =3D=3D READ_REPLICATION_SLOT_COLS); + } + + dest =3D CreateDestReceiver(DestRemoteSimple); + tstate =3D begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); + do_tup_output(tstate, values, nulls); + end_tup_output(tstate); +} + =20 /* * Handle TIMELINE_HISTORY command. @@ -1622,6 +1708,13 @@ exec_replication_command(const char *cmd_string) EndReplicationCommand(cmdtag); break; =20 + case T_ReadReplicationSlotCmd: + cmdtag =3D "READ_REPLICATION_SLOT"; + set_ps_display(cmdtag); + ReadReplicationSlot((ReadReplicationSlotCmd *) cmd_node); + EndReplicationCommand(cmdtag); + break; + case T_BaseBackupCmd: cmdtag =3D "BASE_BACKUP"; set_ps_display(cmdtag); diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/00= 1_stream_rep.pl index 9916a36012..8fb7487857 100644 --- a/src/test/recovery/t/001_stream_rep.pl +++ b/src/test/recovery/t/001_stream_rep.pl @@ -6,7 +6,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests =3D> 49; +use Test::More tests =3D> 53; =20 # Initialize primary node my $node_primary =3D PostgresNode->new('primary'); @@ -254,6 +254,36 @@ ok( $ret =3D=3D 0, "SHOW with superuser-settable parameter, replication role and logical rep= lication" ); =20 +note "testing READ_REPLICATION_SLOT command for replication connection"; + +my $slotname =3D 'test_read_replication_slot_physical'; + +($ret, $stdout, $stderr) =3D $node_primary->psql( + 'postgres', + 'READ_REPLICATION_SLOT non_existent_slot;', + extra_params =3D> [ '-d', $connstr_rep ]); +ok($ret =3D=3D 0, "READ_REPLICATION_SLOT exit code 0 on success"); +like($stdout, qr/^||$/, + "READ_REPLICATION_SLOT returns NULL values if slot does not exist"); + +$node_primary->psql( + 'postgres', + "CREATE_REPLICATION_SLOT $slotname PHYSICAL RESERVE_WAL;", + extra_params =3D> [ '-d', $connstr_rep ]); + +($ret, $stdout, $stderr) =3D $node_primary->psql( + 'postgres', + "READ_REPLICATION_SLOT $slotname;", + extra_params =3D> [ '-d', $connstr_rep ]); +ok($ret =3D=3D 0, "READ_REPLICATION_SLOT success with existing slot"); +like($stdout, qr/^physical\|[^|]*\|1$/, + "READ_REPLICATION_SLOT returns tuple with slot information"); + +$node_primary->psql( + 'postgres', + "DROP_REPLICATION_SLOT $slotname;", + extra_params =3D> [ '-d', $connstr_rep ]); + note "switching to physical replication slot"; =20 # Switch to using a physical replication slot. We can do this without a new diff --git a/src/test/recovery/t/006_logical_decoding.pl b/src/test/recover= y/t/006_logical_decoding.pl index cc116062c2..1b74f38f10 100644 --- a/src/test/recovery/t/006_logical_decoding.pl +++ b/src/test/recovery/t/006_logical_decoding.pl @@ -10,7 +10,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests =3D> 14; +use Test::More tests =3D> 15; use Config; =20 # Initialize primary node @@ -39,6 +39,15 @@ ok( $stderr =3D~ m/replication slot "test_slot" was not created in this database/, "Logical decoding correctly fails to start"); =20 +($result, $stdout, $stderr) =3D $node_primary->psql( + 'template1', + qq[READ_REPLICATION_SLOT test_slot;], + replication =3D> 'database'); +like( + $stderr, + qr/cannot use "READ_REPLICATION_SLOT" with logical replication slots/, + 'READ_REPLICATION_SLOT not supported for logical slots'); + # Check case of walsender not using a database connection. Logical # decoding should not be allowed. ($result, $stdout, $stderr) =3D $node_primary->psql( diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index b95cc88599..37c26ec8ae 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -2067,6 +2067,54 @@ The commands accepted in replication mode are: =20 + + READ_REPLICATION_SLOT slot_name + READ_REPLICATION_SLOT + + + + Read some information associated to a replication slot. Returns a tu= ple + with NULL values if the replication slot does not + exist. This command is currently only supported for physical replica= tion + slots. + + + In response to this command, the server will return a one-row result= set, + containing the following fields: + + + slot_type (text) + + + The replication slot's type, either physical = or + NULL. + + + + + + restart_lsn (text) + + + The replication slot's restart_lsn. + + + + + + restart_tli (int4) + + + The timeline ID associated to restart_lsn, + following the current timeline history. + + + + + + + + START_REPLICATION [ SLOT <= replaceable class=3D"parameter">slot_name ] [ PHYSIC= AL ] XXX/XXX [ TIMELINE tli= ] START_REPLICATION diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs= =2Elist index cb5b5ec74c..a8e4a2afd6 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2128,6 +2128,7 @@ ReadBufferMode ReadBytePtrType ReadExtraTocPtrType ReadFunc +ReadReplicationSlotCmd ReassignOwnedStmt RecheckForeignScan_function RecordCacheEntry --=20 2.33.0 --AOiPlOjeflOit5EO-- --mFeZEW/PmxDzb6Hu Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmFw/AwACgkQnvQgOdby QH0EIw//eUic5duf2kSkqCF4CUrL51zVEo/ApKFNf4XFFYqH6MPS17LNVCNf5PWI MvsFbcPVbM6YSqWCXLi2Fnk3wBpDiIFfJKL5v1OlPyDwewWiYerUycucWnmB3zgb fXBrlS/RX1g0/XAi3e1qgN6WLNstQwygeiQjoyv27i9Eus+MAaVvW8eshqsvWjPi PkiN/VH+t8ySlYyCvgm5OnQVarL5sKugAiUNnqHRxY86lcxrF/WTXlUgltFK1TFk wTae42SQNCAivjWtsoaisecqihKzaP89gn+yOtXzlKI70r/aFJa17nWB74Ygj/g0 FXSFJSc5ANtCfEseiosDl6rOB0q8wV5nx4UQh5OFSiKASU7WCfDSbfo1ENHT7Kod BzyrykoIS3UcqLONO5y2OwdErBHF3SbWauRsF5EXQGH5Sk5XRKnmxzbmpDwskwWG /pN0Ajw0LIlUG/1jSWgGncphOlMckvXNENiYaH0RtiExSmLPc8IgXjP1FC1+K6g5 1zRkU401DWDyVtkPtMiXHcfqXT0sy884bDa0u6qZnlGShhmn7mzld48c5a6I/yNb MjQS5uu4OMHXffmVuQpwBgFdoAB9Bkd50dqWG228c1jx5U7uagePqreLiInduV2I BfRuV/Nd4AUdzi1Yn8y4DSwuw21zOQPp3E/Sl990q8yzYDY6chY= =0yZf -----END PGP SIGNATURE----- --mFeZEW/PmxDzb6Hu--