text: //
  // lqueue.cpp
  //
  // Lock queue code.
  //
  
  #include "private.h"
  #include "ic.h"
  #include "tim.h"
  #include "dim.h"
  
  //+---------------------------------------------------------------------------
  //
  // _EditSessionQiCallback
  //
  //----------------------------------------------------------------------------
  
  HRESULT CAsyncQueueItem::_EditSessionQiCallback(CInputContext *pic, struct _TS_QUEUE_ITEM *pItem, QiCallbackCode qiCode)
  {
      switch (qiCode)
      {
          case QI_ADDREF:
              pItem->state.aqe.paqi->_AddRef();
              break;
  
          case QI_DISPATCH:
              return pItem->state.aqe.paqi->DoDispatch(pic);
  
          case QI_FREE:
              pItem->state.aqe.paqi->_Release();
              break;
      }
      return S_OK;
  }
  
  //+---------------------------------------------------------------------------
  //
  // _CheckReadOnly
  //
  //----------------------------------------------------------------------------
  
  void CAsyncQueueItem::_CheckReadOnly(CInputContext *pic)
  {
      TS_STATUS dcs;
      if (SUCCEEDED(pic->GetStatus(&dcs)))
      {
          if (dcs.dwDynamicFlags & TF_SD_READONLY)
              _item.dwFlags &= ~TF_ES_WRITE;
      }
  }
  
  //+---------------------------------------------------------------------------
  //
  // _QueueItem
  //
  //----------------------------------------------------------------------------
  
  HRESULT CInputContext::_QueueItem(TS_QUEUE_ITEM *pItem, BOOL fForceAsync, HRESULT *phrSession)
  {
      TS_QUEUE_ITEM *pQueueItem;
      int iItem;
      HRESULT hr;
      HRESULT hrRet;
      BOOL fSync;
      BOOL fNeedWriteLock;
      BOOL fSyncLockFailed = FALSE;
  
      if (pItem->dwFlags & TF_ES_WRITE)
      {
          // write access implies property write access
          pItem->dwFlags |= TF_ES_PROPERTY_WRITE;
      }
  
      fSync = (pItem->dwFlags & TF_ES_SYNC);
  
      Assert(!(fSync && fForceAsync)); // doesn't make sense
      
      fNeedWriteLock = _fLockHeld ? (pItem->dwFlags & TF_ES_WRITE) && !(_dwlt & TF_ES_WRITE) : FALSE;
  
      if (fForceAsync)
          goto QueueItem;
  
      if (!fSync &&
          _rgLockQueue.Count() > 0)
      {
          // there's already something in the queue
          // so we can't handle an async request until later
          fForceAsync = TRUE;
          goto QueueItem;
      }
  
      if (_fLockHeld)
      {
          if (fSync)
          {
              // sync
              // we can't handle a sync write req while holding a read lock
              *phrSession = fNeedWriteLock ? TF_E_SYNCHRONOUS : _DispatchQueueItem(pItem);
          }
          else
          {
              // async
              if (fNeedWriteLock)
              {
                  // we need a write lock we don't currently hold
                  fForceAsync = TRUE;
                  goto QueueItem;
              }
  
              *phrSession = _DispatchQueueItem(pItem);
          }
  
          return S_OK;
      }
  
  QueueItem:
      if ((pQueueItem = _rgLockQueue.Append(1)) == NULL)
      {
          *phrSession = E_FAIL;
          return E_OUTOFMEMORY;
      }
  
      *pQueueItem = *pItem;
      _AddRefQueueItem(pQueueItem);
  
      hrRet = S_OK;
      *phrSession = TF_S_ASYNC;
  
      if (!fForceAsync)
      {
          Assert(!_fLockHeld);
  
          _fLockHeld = TRUE;
          _dwlt = pItem->dwFlags;
  
          hrRet = SafeRequestLock(_ptsi, pItem->dwFlags & ~TF_ES_PROPERTY_WRITE, phrSession);
  
          _fLockHeld = FALSE;
  
          // possibly this was a synch request, but the app couldn't grant it
          // now we need to make sure the queue item is cleared
          if ((iItem = _rgLockQueue.Count() - 1) >= 0)
          {
              TS_QUEUE_ITEM *pItemTemp;
              pItemTemp = _rgLockQueue.GetPtr(iItem);
              if (pItemTemp->dwFlags & TF_ES_SYNC)
              {
                  Assert(*phrSession == TS_E_SYNCHRONOUS); // only reason why item is still in queue should be app lock rejection
  
                  _ReleaseQueueItem(pItemTemp);
                  _rgLockQueue.Remove(iItem, 1);
  
                  fSyncLockFailed = TRUE;
              }
          }
  
          if (_ptsi == NULL)
          {
              // someone popped this ic during the RequestLock above
              goto Exit;
          }
      }
  
      // make sure synchronous edit session gets cleared off the queue no matter what after the lock request!
      _Dbg_AssertNoSyncQueueItems();
  
      // this shouldn't go reentrant, but just to be safe do it last
      if (_fLockHeld)
      {
          if (fForceAsync && fNeedWriteLock)
          {
              SafeRequestLock(_ptsi, TS_LF_READWRITE, &hr); // Issue: try to recover if app goes reentrant?
              Assert(hr == TS_S_ASYNC); // app should have granted this async
          }
      }
      else if (!fSyncLockFailed)
      {
          // we don't hold a lock currently
          if (fForceAsync || (fSync && _rgLockQueue.Count() > 0))
          {
              // ask for another lock later, if there are pending async items in the queue
              _PostponeLockRequest(pItem->dwFlags);
          }
      }
  
  Exit:
      return hrRet;
  }
  
  //+---------------------------------------------------------------------------
  //
  // _EmptyLockQueue
  //
  //----------------------------------------------------------------------------
  
  HRESULT CInputContext::_EmptyLockQueue(DWORD dwLockFlags, BOOL fAppChangesSent)
  {
      TS_QUEUE_ITEM *pItem;
      TS_QUEUE_ITEM item;
      int iItem;
      HRESULT hr;
  
      Assert(_fLockHeld); // what are we doing emptying the queue without a lock?!
  
      if ((iItem = _rgLockQueue.Count() - 1) < 0)
          return S_OK;
  
      //
      // special case: there may be a single synchronous es at the end of the queue
      //
  
      pItem = _rgLockQueue.GetPtr(iItem);
  
      if (pItem->dwFlags & TF_ES_SYNC)
      {
          item = *pItem;
          _rgLockQueue.Remove(iItem, 1);
  
          if (fAppChangesSent)
          {
              Assert(0); // this is suspicious...the app should not let this happen.
                         // What's happened: the app had some pending changes, but hadn't responed to
                         // our lock requests yet.  Then some TIP asked for a sync lock, which we just got.
                         // Normally, it is unlikely that an app would still have pending changes by the time
                         // a tip fires off -- the app should clear any pending changes before starting a
                         // transaction like a key event or reconversion.  However, another possibility is
                         // that a rogue TIP is using the sync flag for a private event, which is discouraged
                         // because it could fail here.
              // in any case, we won't continue until the app changes have been dealt with...must back out with an error.
              return E_UNEXPECTED;
          }
  
          if ((item.dwFlags & TF_ES_WRITE) && !(dwLockFlags & TF_ES_WRITE))
          {
              Assert(0); // app granted wrong access?
              return E_UNEXPECTED;
          }
          Assert(!(item.dwFlags & TF_ES_WRITE) || (item.dwFlags & TF_ES_PROPERTY_WRITE)); // write implies property write
  
          hr = _DispatchQueueItem(&item);
          _ReleaseQueueItem(&item);
  
          return hr;
      }
  
      //
      // handle any asynch requests
      //
  
      while (_rgLockQueue.Count() > 0)
      {
          pItem = _rgLockQueue.GetPtr(0);
  
          Assert(!(pItem->dwFlags & TF_ES_SYNC)); // should never see synch item here!
  
          // make sure the lock we've been given is good enough for the queued es
          if ((pItem->dwFlags & TF_ES_WRITE) && !(dwLockFlags & TF_ES_WRITE))
          {
              // ask for an upgrade anyways, to try to recover
              SafeRequestLock(_ptsi, TS_LF_READWRITE, &hr); // Issue: try to recover if app goes reentrant?
              Assert(hr == TS_S_ASYNC); // app should have granted this async
              break;
          }
  
          item = *pItem;
          _rgLockQueue.Remove(0, 1);
          _DispatchQueueItem(&item);
          _ReleaseQueueItem(&item);
      }
  
      return S_OK;
  }
  
  //+---------------------------------------------------------------------------
  //
  // _AbortQueueItems
  //
  //----------------------------------------------------------------------------
  
  void CInputContext::_AbortQueueItems()
  {
      TS_QUEUE_ITEM *pItem;
      int i;
  
      for (i=0; i<_rgLockQueue.Count(); i++)
      {
          pItem = _rgLockQueue.GetPtr(i);
  
          Assert(!(pItem->dwFlags & TF_ES_SYNC)); // should never see synch item here!
  
          _ReleaseQueueItem(pItem);
      }
  
      _rgLockQueue.Clear();
  
      if (_dwPendingLockRequest)
      {
          SYSTHREAD *psfn = GetSYSTHREAD();
  
          if (psfn)
              psfn->_dwLockRequestICRef--;
  
          _dwPendingLockRequest = 0;
      }
  }
  
  //+---------------------------------------------------------------------------
  //
  // _PostponeLockRequest
  //
  //----------------------------------------------------------------------------
  
  void CInputContext::_PostponeLockRequest(DWORD dwFlags)
  {
      dwFlags &= TF_ES_READWRITE;
  
      Assert(dwFlags != 0);
  
      // we don't need to upgrade the req because we can do that inside the
      // lock grant more efficiently
      if (_dwPendingLockRequest == 0)
      {
          SYSTHREAD *psfn = GetSYSTHREAD();
  
          if (!psfn)
              return;
     
          if (!_nLockReqPostDisableRef && !psfn->_fLockRequestPosted)
          {
              if (!PostThreadMessage(GetCurrentThreadId(), g_msgPrivate, TFPRIV_LOCKREQ, 0))
              {
                  return;
              }
  
              psfn->_fLockRequestPosted = TRUE;
          }
  
          psfn->_dwLockRequestICRef++;
      }
  
      _dwPendingLockRequest |= dwFlags;
  }
  
  
  //+---------------------------------------------------------------------------
  //
  // _PostponeLockRequestCallback
  //
  //----------------------------------------------------------------------------
  
  /* static */
  void CInputContext::_PostponeLockRequestCallback(SYSTHREAD *psfn, CInputContext *pic)
  {
      CThreadInputMgr *tim;
      CDocumentInputManager *dim;
      int iDim;
      int iContext;
      DWORD dwFlags;
      HRESULT hr;
  
      Assert(psfn);
  
  
      if (!psfn->_dwLockRequestICRef)
          return;
  
      // need to verify pic is still valid
      tim = CThreadInputMgr::_GetThisFromSYSTHREAD(psfn);
      if (!tim)
          return;
  
      for (iDim = 0; iDim < tim->_rgdim.Count(); iDim++)
      {
          dim = tim->_rgdim.Get(iDim);
  
          for (iContext = 0; iContext <= dim->_GetCurrentStack(); iContext++)
          {
              CInputContext *picCur = dim->_GetIC(iContext);
              if (!pic || (picCur == pic))
              {
                  // we found this ic, it's valid
                  dwFlags = picCur->_dwPendingLockRequest;
                  if (dwFlags)
                  {
                      picCur->_dwPendingLockRequest = 0;
  
                      Assert(psfn->_dwLockRequestICRef > 0);
                      psfn->_dwLockRequestICRef--;
  
                      SafeRequestLock(picCur->_ptsi, dwFlags, &hr);
                  }
  
                  if (pic)
                      return;
              }
          }
      }
  }
  
  //+---------------------------------------------------------------------------
  //
  // EnableLockRequestPosting
  //
  //----------------------------------------------------------------------------
  
  HRESULT CInputContext::EnableLockRequestPosting(BOOL fEnable)
  {
      if (!fEnable)
      {
         _nLockReqPostDisableRef++;
      }
      else
      {
         if (_nLockReqPostDisableRef > 0)
             _nLockReqPostDisableRef--;
      }
      return S_OK;
  }
  