2014-11-02
2014-11-02 22:11:44 by Stephen R. van den Berg <srb@cuci.nl>
-
615d42b6fc7f114031e9cbb9030455436acac711
(330 lines)
(+285/-45)
[
Show
| Annotate
]
Branch: 8.1
pgsql: Further finetuning, fix normal query() race condition, thread safe.
Make the result object threadsafe (e.g. calling fetch_fields() and
fetch_row() simultaneously from multiple threads on the same result
object is supported).
12:
//! It even runs in non-callback mode.
//!
- protected Thread.Mutex backendmux;
- final Pike.Backend local_backend;
+ protected Thread.Mutex backendmux = Thread.Mutex();
+ final Pike.Backend local_backend = Pike.SmallBackend();
protected int clientsregistered;
-
+ multiset cachealways=(<"BEGIN","begin","END","end","COMMIT","commit">);
+ object createprefix
+ =Regexp("^[ \t\f\r\n]*[Cc][Rr][Ee][Aa][Tt][Ee][ \t\f\r\n]");
+ protected object dontcacheprefix
+ =Regexp("^[ \t\f\r\n]*([Ff][Ee][Tt][Cc][Hh]|[Cc][Oo][Pp][Yy])[ \t\f\r\n]");
+ protected object execfetchlimit
+ =Regexp("^[ \t\f\r\n]*(([Uu][Pp][Dd][Aa]|[Dd][Ee][Ll][Ee])[Tt][Ee]|\
+ [Ii][Nn][Ss][Ee][Rr][Tt])[ \t\f\r\n]|\
+ [ \t\f\r\n][Ll][Ii][Mm][Ii][Tt][ \t\f\r\n]+[12][; \t\f\r\n]*$");
+
+ final void closestatement(object plugbuffer,string oldprep) {
+ if(oldprep) {
+ PD("Close statement %s\n",oldprep);
+ plugbuffer->add_int8('C')->add_hstring(({'S',oldprep,0}),4,4);
+ }
+ }
+
protected void run_local_backend() {
Thread.MutexKey lock;
int looponce;
33:
}
protected void create() {
- backendmux = Thread.Mutex();
- local_backend = Pike.SmallBackend();
+
}
final void register_backend() {
55:
}
}
+ final int oidformat(int oid) {
+ switch(oid) {
+ case BOOLOID:
+ case BYTEAOID:
+ case CHAROID:
+ case INT8OID:
+ case INT2OID:
+ case INT4OID:
+ case TEXTOID:
+ case OIDOID:
+ case XMLOID:
+ case MACADDROID:
+ case BPCHAROID:
+ case VARCHAROID:
+ case CTIDOID:
+ case UUIDOID:
+ return 1; //binary
+ }
+ return 0; // text
+ }
+
protected sctype mergemode(PGassist realbuffer,sctype mode) {
if(mode>realbuffer->stashflushmode)
realbuffer->stashflushmode=mode;
86:
Thread.MutexKey lock=realbuffer->stashupdate->lock();
if(portal)
realbuffer->stashqueue->write(portal);
- if(mode==flushlogsend)
- mode=flushsend, realbuffer->stashqueue->write(1);
+
realbuffer->stash->add(this);
mode=mergemode(realbuffer,mode);
if(!--realbuffer->stashcount)
106:
inherit Stdio.Buffer:i;
inherit Stdio.Buffer:o;
- protected Thread.Condition condition;
- protected Thread.Mutex mux;
+ protected Thread.Condition fillread;
+ protected Thread.Mutex fillreadmux;
protected Thread.Queue qportals;
final Stdio.File socket;
protected int towrite;
158: Inside #if defined(PG_DEBUG)
if(howmuch<0)
error("Out of range %d\n",howmuch);
#endif
- if(condition) {
+ if(fillread) {
array cid=local_backend->call_out(gottimeout,timeout);
- Thread.MutexKey lock=mux->lock();
- condition.wait(lock);
+ Thread.MutexKey lock=fillreadmux->lock();
+ fillread.wait(lock);
lock=0;
local_backend->remove_call_out(cid);
} else
170:
}
protected int read_cb(mixed id,mixed b) {
- Thread.MutexKey lock=mux->lock();
- if(condition)
- condition.signal();
+ Thread.MutexKey lock=fillreadmux->lock();
+ if(fillread)
+ fillread.signal();
lock=0;
return 0;
}
protected int write_cb() {
towrite-=output_to(socket,towrite);
- if(!condition && !sizeof(this))
+ if(!fillread && !sizeof(this))
socket->close();
return 0;
}
237:
}
final void sendterminate() {
- destruct(condition); // Delayed close() after flushing the output buffer
+ destruct(fillread); // Delayed close() after flushing the output buffer
}
final int close() {
295:
i::create(); o::create();
qportals = _qportals;
synctransact = 1;
- condition=Thread.Condition();
- mux=Thread.Mutex();
+ fillread=Thread.Condition();
+ fillreadmux=Thread.Mutex();
gottimeout=sendcmd; // Preset it with a NOP
timeout=128; // Just a reasonable amount
socket=Stdio.File();
333:
int _inflight;
int _portalbuffersize;
int _synctransact;
+ Thread.Condition _ddescribe;
+ Thread.Mutex _ddescribemux;
Thread.MutexKey _unnamedportalkey,_unnamedstatementkey;
protected Thread.Mutex closemux;
array _params;
string _statuscmdcomplete;
string _query;
Thread.Queue _datarows;
- array(mapping(string:mixed)) _datarowdesc=({});
+ array(mapping(string:mixed)) _datarowdesc;
int _oldpbpos;
object _plugbuffer;
string _preparedname;
359: Inside #if defined(PG_DEBUGMORE)
#ifdef PG_DEBUGMORE
_query,
#endif
- _portalname,sizeof(_datarowdesc),
+ _portalname,_datarowdesc&&sizeof(_datarowdesc),
_statuscmdcomplete||"");
break;
}
372:
c = _c;
_query = query;
_datarows = Thread.Queue(); numrows = UNDEFINED;
+ _ddescribe=Thread.Condition();
+ _ddescribemux=Thread.Mutex();
closemux=Thread.Mutex();
_portalbuffersize=portalbuffersize;
_alltext = !alltyped;
407:
return rows;
}
+ protected void waitfordescribe() {
+ Thread.MutexKey lock=_ddescribemux->lock();
+ if(!_datarowdesc)
+ _ddescribe->wait(lock);
+ lock=0;
+ }
+
//! @seealso
//! @[Sql.sql_result()->num_fields()]
int num_fields() {
-
+ if(!_datarowdesc)
+ waitfordescribe();
+ trydelayederror();
return sizeof(_datarowdesc);
}
//! @seealso
//! @[Sql.sql_result()->num_rows()]
int num_rows() {
- int numrows;
- if(_statuscmdcomplete)
- sscanf(_statuscmdcomplete,"%*s %d",numrows);
- return numrows;
+ trydelayederror();
+ return _rowsreceived;
}
protected inline void trydelayederror() {
437:
//! @seealso
//! @[Sql.sql_result()->fetch_fields()]
array(mapping(string:mixed)) fetch_fields() {
+ if(!_datarowdesc)
+ waitfordescribe();
trydelayederror();
return _datarowdesc+({});
}
- void _openportal() {
+ final void _setrowdesc(array(mapping(string:mixed)) datarowdesc) {
+ _datarowdesc=datarowdesc;
+ Thread.MutexKey lock=_ddescribemux->lock();
+ _ddescribe->broadcast();
+ lock=0;
+ }
+
+ final void _preparebind() {
+ array dtoid=_tprepared.datatypeoid;
+ array(string|int) paramValues=_params?_params[2]:({});
+ if(sizeof(dtoid)!=sizeof(paramValues))
+ SUSERERROR("Invalid number of bindings, expected %d, got %d\n",
+ sizeof(dtoid),sizeof(paramValues));
+ #ifdef PG_DEBUGMORE
+ PD("ParamValues to bind: %O\n",paramValues);
+ #endif
+ object plugbuffer=Stdio.Buffer();
+ plugbuffer->add(_portalname=
+ (_unnamedportalkey=_pgsqlsess._unnamedportalmux->trylock(1))
+ ? "" : PORTALPREFIX+int2hex(_pgsqlsess._pportalcount++) )->add_int8(0)
+ ->add(_preparedname)->add_int8(0)->add_int16(sizeof(paramValues));
+ foreach(dtoid;;int textbin)
+ plugbuffer->add_int16(oidformat(textbin));
+ plugbuffer->add_int16(sizeof(paramValues));
+ string cenc=_pgsqlsess._runtimeparameter[CLIENT_ENCODING];
+ foreach(paramValues;int i;mixed value) {
+ if(undefinedp(value))
+ plugbuffer->add_int32(-1); // NULL
+ else if(stringp(value) && !sizeof(value)) {
+ int k=0;
+ switch(dtoid[i]) {
+ default:
+ k=-1; // cast empty strings to NULL for non-string types
+ case BYTEAOID:
+ case TEXTOID:
+ case XMLOID:
+ case BPCHAROID:
+ case VARCHAROID:;
+ }
+ plugbuffer->add_int32(k);
+ } else
+ switch(dtoid[i]) {
+ case TEXTOID:
+ case BPCHAROID:
+ case VARCHAROID: {
+ if(!value) {
+ plugbuffer->add_int32(-1);
+ break;
+ }
+ value=(string)value;
+ switch(cenc) {
+ case UTF8CHARSET:
+ value=string_to_utf8(value);
+ break;
+ default:
+ if(String.width(value)>8) {
+ SUSERERROR("Don't know how to convert %O to %s encoding\n",
+ value,cenc);
+ value="";
+ }
+ }
+ plugbuffer->add_hstring(value,4);
+ break;
+ }
+ default: {
+ if(!value) {
+ plugbuffer->add_int32(-1);
+ break;
+ }
+ value=(string)value;
+ if(String.width(value)>8)
+ if(dtoid[i]==BYTEAOID)
+ value=string_to_utf8(value);
+ else {
+ SUSERERROR("Wide string %O not supported for type OID %d\n",
+ value,dtoid[i]);
+ value="";
+ }
+ plugbuffer->add_hstring(value,4);
+ break;
+ }
+ case BOOLOID:plugbuffer->add_int32(1);
+ do {
+ int tval;
+ if(stringp(value))
+ tval=value[0];
+ else if(!intp(value)) {
+ value=!!value; // cast to boolean
+ break;
+ } else
+ tval=value;
+ switch(tval) {
+ case 'o':case 'O':
+ catch {
+ tval=value[1];
+ value=tval=='n'||tval=='N';
+ };
+ break;
+ default:
+ value=1;
+ break;
+ case 0:case '0':case 'f':case 'F':case 'n':case 'N':
+ value=0;
+ break;
+ }
+ } while(0);
+ plugbuffer->add_int8(value);
+ break;
+ case CHAROID:
+ if(intp(value))
+ plugbuffer->add_hstring(value,4);
+ else {
+ value=(string)value;
+ switch(sizeof(value)) {
+ default:
+ SUSERERROR(
+ "\"char\" types must be 1 byte wide, got %O\n",value);
+ case 0:
+ plugbuffer->add_int32(-1); // NULL
+ break;
+ case 1:
+ plugbuffer->add_hstring(value[0],4);
+ }
+ }
+ break;
+ case INT8OID:
+ plugbuffer->add_int32(8)->add_int((int)value,8);
+ break;
+ case OIDOID:
+ case INT4OID:
+ plugbuffer->add_int32(4)->add_int32((int)value);
+ break;
+ case INT2OID:
+ plugbuffer->add_int32(2)->add_int16((int)value);
+ break;
+ }
+ }
+ _plugbuffer=plugbuffer;
+ if(_tprepared)
+ if(_tprepared.datarowdesc)
+ _dodatarows();
+ else if(dontcacheprefix->match(_query)) // Don't cache FETCH/COPY
+ m_delete(_pgsqlsess->_prepareds,_query),_tprepared=0;
+ }
+
+ final void _processrowdesc(array(mapping(string:mixed)) datarowdesc) {
+ _setrowdesc(datarowdesc);
+ mapping(string:mixed) tp=_tprepared; // FIXME Is caching this worthwhile?
+ if(!tp || !tp.datarowdesc)
+ Thread.Thread(_dodatarows,this);
+ if(tp)
+ tp.datarowdesc=datarowdesc;
+ }
+
+ final void _dodatarows() {
+ object plugbuffer=_plugbuffer;
+ _plugbuffer=0;
+ plugbuffer->add_int16(sizeof(_datarowdesc));
+ foreach(_datarowdesc;;mapping col)
+ plugbuffer->add_int16(oidformat(col.type));
+ PD("Bind portal %O statement %O\n",_portalname,_preparedname);
+ _fetchlimit=_pgsqlsess->_fetchlimit;
+ _openportal();
+ object bindbuffer=c->start(1);
+ _unnamedstatementkey=0;
+ bindbuffer->add_int8('B')->add_hstring(plugbuffer,4,4);
+ if(!_tprepared)
+ closestatement(bindbuffer,_preparedname);
+ _sendexecute(_pgsqlsess->_fetchlimit
+ && !(cachealways[_query]
+ || sizeof(_query)>=MINPREPARELENGTH &&
+ execfetchlimit->match(_query))
+ && FETCHLIMITLONGRUN,bindbuffer);
+ }
+
+ final void _openportal() {
_pgsqlsess->_portalsinflight++;
Thread.MutexKey lock=closemux->lock();
_state=bound;
449:
_statuscmdcomplete=UNDEFINED;
}
- sctype _closeportal(void|PGplugbuffer plugbuffer) {
+ final void _purgeportal() {
+ _unnamedportalkey=0;
+ Thread.MutexKey lock=closemux->lock();
+ _fetchlimit=0; // disables further Executes
+ switch(_state) {
+ case copyinprogress:
+ case bound:
+ --_pgsqlsess->_portalsinflight;
+ }
+ _state=closed;
+ lock=0;
+ }
+
+ final sctype _closeportal(PGplugbuffer plugbuffer) {
sctype retval=keep;
PD("%O Try Closeportal %d\n",_portalname,_state);
Thread.MutexKey lock=closemux->lock();
_fetchlimit=0; // disables further Executes
switch(_state) {
-
+ case portalinit:
+ _state=closed;
+ break;
case copyinprogress:
- if(plugbuffer) {
+
PD("CopyDone\n");
plugbuffer->add("c\0\0\0\4");
- }
+
case bound:
_state=closed;
lock=0;
PD("Close portal %O\n",_portalname);
if(sizeof(_portalname)) {
- if(plugbuffer) {
+
plugbuffer->add_int8('C')->add_hstring(({'P',_portalname,0}),4,4);
retval=flushsend;
- }
+
} else
_unnamedportalkey=0;
- if(!--_pgsqlsess->_portalsinflight && plugbuffer) {
+ if(!--_pgsqlsess->_portalsinflight) {
_pgsqlsess->_readyforquerycount++;
_pgsqlsess->_pportalcount=0;
retval=syncsend;
481:
return retval;
}
- final void _processdataready(int gfetchlimit) {
+ final void _processdataready() {
_rowsreceived++;
if(_rowsreceived==1)
PD("<%O _fetchlimit %d=min(%d||1,%d), _inflight %d\n",_portalname,
_fetchlimit,(_portalbuffersize>>1)*_rowsreceived/_bytesreceived,
- gfetchlimit,_inflight);
+ _pgsqlsess._fetchlimit,_inflight);
if(_fetchlimit) {
_fetchlimit=
- min((_portalbuffersize>>1)*_rowsreceived/_bytesreceived||1,gfetchlimit);
+ min((_portalbuffersize>>1)*_rowsreceived/_bytesreceived||1,
+ _pgsqlsess._fetchlimit);
Thread.MutexKey lock=closemux->lock();
if(_fetchlimit && _inflight<=_fetchlimit-1)
_sendexecute(_fetchlimit);
500:
}
}
- void _releasesession() {
+ final void _releasesession() {
_inflight=0;
- _datarows->write(1);
+ _datarows->write(1); // Signal EOF
object plugbuffer=c->start(1);
plugbuffer->sendcmd(_closeportal(plugbuffer));
_pgsqlsess=UNDEFINED;
540:
int|array datarow;
if(arrayp(datarow=_datarows->try_read()))
return datarow;
- if(!datarow && !eoffound
+ if(!eoffound) {
+ if(!datarow
&& (PD("%O Block for datarow\n",_portalname),
arrayp(datarow=_datarows->read())))
return datarow;
- trydelayederror();
+
eoffound=1;
-
+ _datarows->write(1); // Signal EOF for other threads
+ }
+ trydelayederror();
return 0;
}