Branch: Tag:

2014-11-09

2014-11-09 22:05:08 by Stephen R. van den Berg <srb@cuci.nl>

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;    }