From 8499e93f4ddb19bb66c7ab57add1d6d803c4fc26 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 25 Sep 2013 20:40:07 -0700 Subject: fix crashers with wifi/virtual displays Bug: 10647742 Change-Id: I4b8ed9da52ef95af3a3b3a04b98514a3776a674d --- .../surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp | 11 +++++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp index c06043d..88ef392 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp @@ -258,6 +258,17 @@ status_t VirtualDisplaySurface::dequeueBuffer(int* pslot, sp* fence, bool Source source = fbSourceForCompositionType(mCompositionType); if (source == SOURCE_SINK) { + + if (mOutputProducerSlot < 0) { + // Last chance bailout if something bad happened earlier. For example, + // in a GLES configuration, if the sink disappears then dequeueBuffer + // will fail, the GLES driver won't queue a buffer, but SurfaceFlinger + // will soldier on. So we end up here without a buffer. There should + // be lots of scary messages in the log just before this. + VDS_LOGE("dequeueBuffer: no buffer, bailing out"); + return NO_MEMORY; + } + // We already dequeued the output buffer. If the GLES driver wants // something incompatible, we have to cancel and get a new one. This // will mean that HWC will see a different output buffer between diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 879c911..f83cc06 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -963,6 +963,12 @@ void SurfaceFlinger::postFramebuffer() hwc.commit(); } + // make the default display current because the VirtualDisplayDevice code cannot + // deal with dequeueBuffer() being called outside of the composition loop; however + // the code below can call glFlush() which is allowed (and does in some case) call + // dequeueBuffer(). + getDefaultDisplayDevice()->makeCurrent(mEGLDisplay, mEGLContext); + for (size_t dpy=0 ; dpy hw(mDisplays[dpy]); const Vector< sp >& currentLayers(hw->getVisibleLayersSortedByZ()); -- cgit v1.1 From 6f2bc240e96e82058135f0eccf08594e4301ef3d Mon Sep 17 00:00:00 2001 From: Jesse Hall Date: Fri, 27 Sep 2013 22:10:47 -0700 Subject: Fix two EGLConfig selection bugs This fixes two bugs introduced by Change-Id: Ia8cc084c02a0e3de910def024da8a08d02bbd89d (a) There is no invalid EGLConfig value, in particular zero is valid. Checking return values of eglGetConfigs and eglChooseConfig is the only way to determine success. (b) The "simple" EGLConfig query used as the emulator fallback should not include EGL_RECORDABLE; the emulator doesn't have it. Bug: 10935622 Change-Id: Ib798a24e7cf06a679811c46eaa45d39174a715ec --- services/surfaceflinger/SurfaceFlinger.cpp | 39 ++++++++++++++---------------- services/surfaceflinger/SurfaceFlinger.h | 4 +-- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f83cc06..4f139fe 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -366,12 +366,10 @@ public: operator EGLint const* () const { return &mList.keyAt(0).v; } }; -EGLConfig SurfaceFlinger::selectEGLConfig(EGLDisplay display, EGLint nativeVisualId, - EGLint renderableType) { +status_t SurfaceFlinger::selectEGLConfig(EGLDisplay display, EGLint nativeVisualId, + EGLint renderableType, EGLConfig* config) { // select our EGLConfig. It must support EGL_RECORDABLE_ANDROID if // it is to be used with WIFI displays - EGLConfig config; - EGLint dummy; status_t err; EGLint wantedAttribute; EGLint wantedAttributeValue; @@ -390,22 +388,18 @@ EGLConfig SurfaceFlinger::selectEGLConfig(EGLDisplay display, EGLint nativeVisua } else { // if no renderable type specified, fallback to a simplified query - attribs[EGL_RECORDABLE_ANDROID] = EGL_TRUE; wantedAttribute = EGL_NATIVE_VISUAL_ID; wantedAttributeValue = nativeVisualId; } err = selectConfigForAttribute(display, attribs, wantedAttribute, - wantedAttributeValue, &config); - if (!err) - goto success; - - return 0; - -success: - if (eglGetConfigAttrib(display, config, EGL_CONFIG_CAVEAT, &dummy)) - ALOGW_IF(dummy == EGL_SLOW_CONFIG, "EGL_SLOW_CONFIG selected!"); - return config; + wantedAttributeValue, config); + if (err == NO_ERROR) { + EGLint caveat; + if (eglGetConfigAttrib(display, *config, EGL_CONFIG_CAVEAT, &caveat)) + ALOGW_IF(caveat == EGL_SLOW_CONFIG, "EGL_SLOW_CONFIG selected!"); + } + return err; } void SurfaceFlinger::init() { @@ -413,6 +407,7 @@ void SurfaceFlinger::init() { ALOGI( "SurfaceFlinger's main thread ready to run. " "Initializing graphics H/W..."); + status_t err; Mutex::Autolock _l(mStateLock); // initialize EGL for the default display @@ -425,21 +420,23 @@ void SurfaceFlinger::init() { *static_cast(this)); // First try to get an ES2 config - mEGLConfig = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(), EGL_OPENGL_ES2_BIT); + err = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(), EGL_OPENGL_ES2_BIT, + &mEGLConfig); - if (!mEGLConfig) { + if (err != NO_ERROR) { // If ES2 fails, try ES1 - mEGLConfig = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(), EGL_OPENGL_ES_BIT); + err = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(), + EGL_OPENGL_ES_BIT, &mEGLConfig); } - if (!mEGLConfig) { + if (err != NO_ERROR) { // still didn't work, probably because we're on the emulator... // try a simplified query ALOGW("no suitable EGLConfig found, trying a simpler query"); - mEGLConfig = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(), 0); + err = selectEGLConfig(mEGLDisplay, mHwc->getVisualID(), 0, &mEGLConfig); } - if (!mEGLConfig) { + if (err != NO_ERROR) { // this EGL is too lame for android LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up"); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 989e439..0e9955c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -317,8 +317,8 @@ private: */ static status_t selectConfigForAttribute(EGLDisplay dpy, EGLint const* attrs, EGLint attribute, EGLint value, EGLConfig* outConfig); - static EGLConfig selectEGLConfig(EGLDisplay disp, EGLint visualId, - EGLint renderableType); + static status_t selectEGLConfig(EGLDisplay disp, EGLint visualId, + EGLint renderableType, EGLConfig* config); size_t getMaxTextureSize() const; size_t getMaxViewportDims() const; -- cgit v1.1 From 8eb7a481abbbc3cfe713b43158f1913f958357cf Mon Sep 17 00:00:00 2001 From: Jesse Hall Date: Mon, 30 Sep 2013 16:49:30 -0700 Subject: Treat composition frames with no layers as using GLES composition When there are no window layers for a display, SurfaceFlinger clears the undefined region using GLES. Some of the places that check for GLES composition weren't considering this special case, in particular: - We were skipping the eglSwapBuffers() on these frames. - We were putting VirtualDisplaySurface in HWC-only composition mode. This change centralizes the logic for this special case. Bug: 10957068 Change-Id: I2deaf2ed101e8ea76708862a6bb67751b6078794 --- services/surfaceflinger/DisplayHardware/HWComposer.cpp | 9 +++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index c851a2c..7132b2f 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -611,6 +611,10 @@ status_t HWComposer::prepare() { // here we're just making sure that "skip" layers are set // to HWC_FRAMEBUFFER and we're also counting how many layers // we have of each type. + // + // If there are no window layers, we treat the display has having FB + // composition, because SurfaceFlinger will use GLES to draw the + // wormhole region. for (size_t i=0 ; inumHwLayers == (disp.framebufferTarget ? 1 : 0)) { + disp.hasFbComp = true; + } + } else { + disp.hasFbComp = true; } } } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 4f139fe..b5b0f2c 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1524,7 +1524,7 @@ void SurfaceFlinger::doComposeSurfaces(const sp& hw, const HWComposer::LayerListIterator cur = hwc.begin(id); const HWComposer::LayerListIterator end = hwc.end(id); - const bool hasGlesComposition = hwc.hasGlesComposition(id) || (cur==end); + bool hasGlesComposition = hwc.hasGlesComposition(id); if (hasGlesComposition) { if (!hw->makeCurrent(mEGLDisplay, mEGLContext)) { ALOGW("DisplayDevice::makeCurrent failed. Aborting surface composition for display %s", -- cgit v1.1 From d5350dea05571218290c583a9c482749bda9f7cc Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 1 Oct 2013 15:36:52 -0700 Subject: only clear FB when asked for the opaque layer a layer need to be considered NOT opaque if it has a plane-alpha. Bug: 10846930 Change-Id: Ibd8981b63ede4560c7096bacc4cff46a7eb2a8bb --- services/surfaceflinger/SurfaceFlinger.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b5b0f2c..e374548 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1597,9 +1597,10 @@ void SurfaceFlinger::doComposeSurfaces(const sp& hw, const if (!clip.isEmpty()) { switch (cur->getCompositionType()) { case HWC_OVERLAY: { + const Layer::State& state(layer->getDrawingState()); if ((cur->getHints() & HWC_HINT_CLEAR_FB) && i - && layer->isOpaque() + && layer->isOpaque() && (state.alpha == 0xFF) && hasGlesComposition) { // never clear the very first layer since we're // guaranteed the FB is already cleared -- cgit v1.1 From 062019b8ceafe55dc4045de78c3c291a22a6b459 Mon Sep 17 00:00:00 2001 From: Jesse Hall Date: Tue, 1 Oct 2013 17:25:20 -0700 Subject: Set the outbuf acquire fence after we actually have it. In GLES-only mode, we don't have the outbuf acquire fence until after GLES composition is done for the frame. We were setting the fence in HWC's state immediately after dequeueing the buffer from the consumer, before GLES had started. This fence got passed through HWC and on to the consumer, so the consumer was reading the buffer before GLES was done writing to it. Now we update HWC's state just before set(), when we know we have the right fence. Bug: 11000763 Change-Id: Iea9db4c69634c352dc2d600f0bdb6bef2a432636 --- .../surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp index 88ef392..c5a14b0 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp @@ -147,6 +147,10 @@ status_t VirtualDisplaySurface::advanceFrame() { mFbProducerSlot, fbBuffer.get(), mOutputProducerSlot, outBuffer.get()); + // At this point we know the output buffer acquire fence, + // so update HWC state with it. + mHwc.setOutputBuffer(mDisplayId, mOutputFence, outBuffer); + return mHwc.fbPost(mDisplayId, mFbFence, fbBuffer); } @@ -415,7 +419,11 @@ status_t VirtualDisplaySurface::refreshOutputBuffer() { return result; mOutputProducerSlot = mapSource2ProducerSlot(SOURCE_SINK, sslot); - result = mHwc.setOutputBuffer(mDisplayId, mOutputFence, + // On GLES-only frames, we don't have the right output buffer acquire fence + // until after GLES calls queueBuffer(). So here we just set the buffer + // (for use in HWC prepare) but not the fence; we'll call this again with + // the proper fence once we have it. + result = mHwc.setOutputBuffer(mDisplayId, Fence::NO_FENCE, mProducerBuffers[mOutputProducerSlot]); return result; -- cgit v1.1 From 0e9c51ec78df650e2a340978ccce920d419a9fa1 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Wed, 9 Oct 2013 16:38:02 -0700 Subject: Fix blank / partial screenshots The screen capture code wasn't waiting for the render to finish, so sometimes you'd see an empty or partial image. Bug 11131777 Change-Id: Ic64087322ce3bb15bb5f4fb1eb07579880fe6197 --- services/surfaceflinger/SurfaceFlinger.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3733ede..4a1373e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2982,6 +2982,27 @@ status_t SurfaceFlinger::captureScreenImplLocked( renderScreenImplLocked(hw, reqWidth, reqHeight, minLayerZ, maxLayerZ, true); + // Create a sync point and wait on it, so we know the buffer is + // ready before we pass it along. We can't trivially call glFlush(), + // so we use a wait flag instead. + // TODO: pass a sync fd to queueBuffer() and let the consumer wait. + EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, NULL); + if (sync != EGL_NO_SYNC_KHR) { + EGLint result = eglClientWaitSyncKHR(mEGLDisplay, sync, + EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 2000000000 /*2 sec*/); + EGLint eglErr = eglGetError(); + eglDestroySyncKHR(mEGLDisplay, sync); + if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ALOGW("captureScreen: fence wait timed out"); + } else { + ALOGW_IF(eglErr != EGL_SUCCESS, + "captureScreen: error waiting on EGL fence: %#x", eglErr); + } + } else { + ALOGW("captureScreen: error creating EGL fence: %#x", eglGetError()); + // not fatal + } + if (DEBUG_SCREENSHOTS) { uint32_t* pixels = new uint32_t[reqWidth*reqHeight]; getRenderEngine().readPixels(0, 0, reqWidth, reqHeight, pixels); -- cgit v1.1 From a8b5982c32f6232428e9dcab691bb0bca6f72d63 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Thu, 10 Oct 2013 18:54:12 -0700 Subject: Eliminate latency when resampling is disabled Since the latency was introduced to avoid egregious errors during resampling, remove it when we aren't resampling. Change-Id: Ia8e9a9afb1e16212fec81a8c9c661c598e1b0ebf --- libs/input/InputTransport.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index d6507f4..9bd7fc6 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -376,13 +376,13 @@ InputConsumer::~InputConsumer() { bool InputConsumer::isTouchResamplingEnabled() { char value[PROPERTY_VALUE_MAX]; - int length = property_get("debug.inputconsumer.resample", value, NULL); + int length = property_get("ro.input.noresample", value, NULL); if (length > 0) { - if (!strcmp("0", value)) { + if (!strcmp("1", value)) { return false; } - if (strcmp("1", value)) { - ALOGD("Unrecognized property value for 'debug.inputconsumer.resample'. " + if (strcmp("0", value)) { + ALOGD("Unrecognized property value for 'ro.input.noresample'. " "Use '1' or '0'."); } } @@ -511,7 +511,7 @@ status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, status_t result; for (size_t i = mBatches.size(); i-- > 0; ) { Batch& batch = mBatches.editItemAt(i); - if (frameTime < 0) { + if (frameTime < 0 || !mResampleTouch) { result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent); mBatches.removeAt(i); -- cgit v1.1 From 4c4cd9c8cc91ec4d221ceabd58b84ab226bd38f1 Mon Sep 17 00:00:00 2001 From: Aravind Akella Date: Tue, 8 Oct 2013 14:59:26 -0700 Subject: Change API from flush(handle) to flush(). Call flush on all active sensors in the given SensorEventConnection. Change-Id: I4ef2bec80406c517903ab9782dc9eaf3fa8b7f36 --- include/gui/ISensorEventConnection.h | 2 +- include/gui/SensorEventQueue.h | 2 +- libs/gui/ISensorEventConnection.cpp | 6 ++---- libs/gui/SensorEventQueue.cpp | 4 ++-- services/sensorservice/SensorService.cpp | 22 ++++++++++++++++++---- services/sensorservice/SensorService.h | 2 +- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/include/gui/ISensorEventConnection.h b/include/gui/ISensorEventConnection.h index 00eecc4..f64c6b8 100644 --- a/include/gui/ISensorEventConnection.h +++ b/include/gui/ISensorEventConnection.h @@ -39,7 +39,7 @@ public: virtual status_t enableDisable(int handle, bool enabled, nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs, int reservedFlags) = 0; virtual status_t setEventRate(int handle, nsecs_t ns) = 0; - virtual status_t flushSensor(int handle) = 0; + virtual status_t flush() = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/gui/SensorEventQueue.h b/include/gui/SensorEventQueue.h index a3a9daa..0bfc7a0 100644 --- a/include/gui/SensorEventQueue.h +++ b/include/gui/SensorEventQueue.h @@ -74,7 +74,7 @@ public: status_t enableSensor(int32_t handle, int32_t samplingPeriodUs, int maxBatchReportLatencyUs, int reservedFlags) const; status_t disableSensor(int32_t handle) const; - status_t flushSensor(int32_t handle) const; + status_t flush() const; private: sp getLooper() const; diff --git a/libs/gui/ISensorEventConnection.cpp b/libs/gui/ISensorEventConnection.cpp index a80c661..28fcb53 100644 --- a/libs/gui/ISensorEventConnection.cpp +++ b/libs/gui/ISensorEventConnection.cpp @@ -77,10 +77,9 @@ public: return reply.readInt32(); } - virtual status_t flushSensor(int handle) { + virtual status_t flush() { Parcel data, reply; data.writeInterfaceToken(ISensorEventConnection::getInterfaceDescriptor()); - data.writeInt32(handle); remote()->transact(FLUSH_SENSOR, data, &reply); return reply.readInt32(); } @@ -122,8 +121,7 @@ status_t BnSensorEventConnection::onTransact( } break; case FLUSH_SENSOR: { CHECK_INTERFACE(ISensorEventConnection, data, reply); - int handle = data.readInt32(); - status_t result = flushSensor(handle); + status_t result = flush(); reply->writeInt32(result); return NO_ERROR; } break; diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp index ab50c1d..c365671 100644 --- a/libs/gui/SensorEventQueue.cpp +++ b/libs/gui/SensorEventQueue.cpp @@ -132,8 +132,8 @@ status_t SensorEventQueue::enableSensor(int32_t handle, int32_t samplingPeriodUs us2ns(maxBatchReportLatencyUs), reservedFlags); } -status_t SensorEventQueue::flushSensor(int32_t handle) const { - return mSensorEventConnection->flushSensor(handle); +status_t SensorEventQueue::flush() const { + return mSensorEventConnection->flush(); } status_t SensorEventQueue::disableSensor(int32_t handle) const { diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index b26e572..af605de 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -679,8 +679,12 @@ status_t SensorService::flushSensor(const sp& connection, int handle) { if (mInitCheck != NO_ERROR) return mInitCheck; SensorInterface* sensor = mSensorMap.valueFor(handle); - if (sensor == NULL) { - return BAD_VALUE; + if (sensor == NULL) { + return BAD_VALUE; + } + if (sensor->getSensor().getType() == SENSOR_TYPE_SIGNIFICANT_MOTION) { + ALOGE("flush called on Significant Motion sensor"); + return INVALID_OPERATION; } SensorDevice& dev(SensorDevice::getInstance()); @@ -934,8 +938,18 @@ status_t SensorService::SensorEventConnection::setEventRate( return mService->setEventRate(this, handle, samplingPeriodNs); } -status_t SensorService::SensorEventConnection::flushSensor(int handle) { - return mService->flushSensor(this, handle); +status_t SensorService::SensorEventConnection::flush() { + Mutex::Autolock _l(mConnectionLock); + status_t err(NO_ERROR); + for (size_t i = 0; i < mSensorInfo.size(); ++i) { + const int handle = mSensorInfo.keyAt(i); + status_t err_flush = mService->flushSensor(this, handle); + if (err_flush != NO_ERROR) { + ALOGE("Flush error handle=%d %s", handle, strerror(-err_flush)); + } + err = (err_flush != NO_ERROR) ? err_flush : err; + } + return err; } // --------------------------------------------------------------------------- diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index 2311bff..2d40071 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -79,7 +79,7 @@ class SensorService : virtual status_t enableDisable(int handle, bool enabled, nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs, int reservedFlags); virtual status_t setEventRate(int handle, nsecs_t samplingPeriodNs); - virtual status_t flushSensor(int handle); + virtual status_t flush(); // Count the number of flush complete events which are about to be dropped in the buffer. // Increment mPendingFlushEventsToSend in mSensorInfo. These flush complete events will be // sent separately before the next batch of events. -- cgit v1.1