There appears to be a problem with how client encoding is handled in the communication from parallel workers. In a parallel worker, the client encoding setting is inherited from its creating process as part of the GUC setup. So any plain-text stuff the parallel worker sends to its leader is actually converted to the client encoding. Since most data is sent in binary format, the plain-text provision applies mainly to notice and error messages. At the other end, error messages are parsed using pq_parse_errornotice(), which internally uses routines that were meant for communication from the client, and therefore will convert everything back from the client encoding to the server encoding. So this whole thing actually happens to work as long as round tripping is possible between the involved encodings.

In cases where it isn't, it's still hard to notice the difference because depending on whether you get a parallel plan or not, the following happens:

not parallel: conversion error happens between server and client, client sees an error message about that

parallel: conversion error happens between worker and leader, worker generates an error message about that, sends it to leader, leader forwards it to client

The client sees the same error message in both cases.

To construct a case where this makes a difference, the leader has to be set up to catch certain errors. Here is an example:

"""
create table test1 (a int, b text);
truncate test1;
insert into test1 values (1, 'a');

create or replace function test1() returns text language plpgsql
as $$
declare
  res text;
begin
  perform from test1 where a = test2();
  return res;
exception when division_by_zero then
  return 'boom';
end;
$$;

create or replace function test2() returns int language plpgsql
parallel safe
as $$
begin
  raise division_by_zero using message = 'Motörhead';
  return 1;
end
$$;

set force_parallel_mode to on;

select test1();
"""

With client_encoding = server_encoding, this will return a single row 'boom'. But with, say, database encoding UTF8 and PGCLIENTENCODING=KOI8R, it will error:

ERROR: 22P05: character with byte sequence 0xef 0xbe 0x83 in encoding "UTF8" has no equivalent in encoding "KOI8R"
CONTEXT:  parallel worker

(Note that changing force_parallel_mode does not force replanning in plpgsql, so if you run test1() first before setting force_parallel_mode, then you won't get the error.)

Attached is a patch to illustrates how this could be fixed. There might be similar issues elsewhere. The notification propagation in particular could be affected.

--
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/access/transam/parallel.c 
b/src/backend/access/transam/parallel.c
index 934dba8..d2105ec 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -775,6 +775,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, 
StringInfo msg)
                                ErrorData       edata;
                                ErrorContextCallback errctx;
                                ErrorContextCallback *save_error_context_stack;
+                               int save_client_encoding;
 
                                /*
                                 * Rethrow the error using the error context 
callbacks that
@@ -787,9 +788,14 @@ HandleParallelMessage(ParallelContext *pcxt, int i, 
StringInfo msg)
                                errctx.previous = pcxt->error_context_stack;
                                error_context_stack = &errctx;
 
+                               save_client_encoding = pg_get_client_encoding();
+                               SetClientEncoding(GetDatabaseEncoding());
+
                                /* Parse ErrorResponse or NoticeResponse. */
                                pq_parse_errornotice(msg, &edata);
 
+                               SetClientEncoding(save_client_encoding);
+
                                /* Death of a worker isn't enough justification 
for suicide. */
                                edata.elevel = Min(edata.elevel, ERROR);
 
@@ -989,6 +995,7 @@ ParallelWorkerMain(Datum main_arg)
        StartTransactionCommand();
        RestoreGUCState(gucspace);
        CommitTransactionCommand();
+       SetClientEncoding(GetDatabaseEncoding());
 
        /* Crank up a transaction state appropriate to a parallel worker. */
        tstatespace = shm_toc_lookup(toc, PARALLEL_KEY_TRANSACTION_STATE);
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to