From: Dmitry Kurochkin Date: Tue, 1 Mar 2011 02:44:52 +0000 (+0300) Subject: IpcIoFile atomic queue v2: X-Git-Tag: take06~58^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b2aa093453e08b3f4d2de96d2e6ca3d05da0c8dd;p=thirdparty%2Fsquid.git IpcIoFile atomic queue v2: * rework ipc/Queue, move implementation to .cc * fix raw pointer use in IpcIoFile --- diff --git a/src/DiskIO/IpcIo/IpcIoFile.cc b/src/DiskIO/IpcIo/IpcIoFile.cc index 4514fa7153..eb51a503f8 100644 --- a/src/DiskIO/IpcIo/IpcIoFile.cc +++ b/src/DiskIO/IpcIo/IpcIoFile.cc @@ -18,26 +18,30 @@ CBDATA_CLASS_INIT(IpcIoFile); IpcIoFile::DiskerQueue *IpcIoFile::diskerQueue = NULL; -IpcIoFile::IpcIoFiles IpcIoFile::ipcIoFiles; +const double IpcIoFile::Timeout = 7; +IpcIoFile::IpcIoFileList IpcIoFile::WaitingForOpen; +IpcIoFile::IpcIoFilesMap IpcIoFile::IpcIoFiles; static bool DiskerOpen(const String &path, int flags, mode_t mode); static void DiskerClose(const String &path); IpcIoFile::IpcIoFile(char const *aDb): - dbName(aDb), - diskId(-1), - workerQueue(NULL), - error_(false), - lastRequestId(0), - olderRequests(&requestMap1), - newerRequests(&requestMap2), + dbName(aDb), diskId(-1), workerQueue(NULL), error_(false), lastRequestId(0), + olderRequests(&requestMap1), newerRequests(&requestMap2), timeoutCheckScheduled(false) { } IpcIoFile::~IpcIoFile() { + if (diskId >= 0) { + const IpcIoFilesMap::iterator i = IpcIoFiles.find(diskId); + // XXX: warn and continue? + Must(i != IpcIoFiles.end()); + Must(i->second == this); + IpcIoFiles.erase(i); + } } void @@ -53,8 +57,12 @@ IpcIoFile::open(int flags, mode_t mode, RefCount callback) return; // XXX: make capacity configurable - diskerQueue = new DiskerQueue(dbName.termedBuf(), Config.workers, 1024); - ipcIoFiles.insert(std::make_pair(KidIdentifier, this)); + diskerQueue = + new DiskerQueue(dbName, Config.workers, sizeof(IpcIoMsg), 1024); + diskId = KidIdentifier; + const bool inserted = + IpcIoFiles.insert(std::make_pair(diskId, this)).second; + Must(inserted); Ipc::HereIamMessage ann(Ipc::StrandCoord(KidIdentifier, getpid())); ann.strand.tag = dbName; @@ -68,15 +76,16 @@ IpcIoFile::open(int flags, mode_t mode, RefCount callback) Ipc::StrandSearchRequest request; request.requestorId = KidIdentifier; - request.data = this; request.tag = dbName; Ipc::TypedMsgHdr msg; request.pack(msg); Ipc::SendMessage(Ipc::coordinatorAddr, msg); - IpcIoPendingRequest *const pending = new IpcIoPendingRequest(this); - trackPendingRequest(*pending); + WaitingForOpen.push_back(this); + + eventAdd("IpcIoFile::OpenTimeout", &IpcIoFile::OpenTimeout, + this, Timeout, 0, false); // "this" pointer is used as id } void @@ -84,18 +93,16 @@ IpcIoFile::openCompleted(const Ipc::StrandSearchResponse *const response) { Must(diskId < 0); // we do not know our disker yet Must(!workerQueue); - // contain single pending open request at this time - newerRequests->clear(); - olderRequests->clear(); - if (!response) { debugs(79,1, HERE << "error: timeout"); error_ = true; } else { diskId = response->strand.kidId; if (diskId >= 0) { - workerQueue = DiskerQueue::Attach(dbName.termedBuf(), KidIdentifier - 1); - ipcIoFiles.insert(std::make_pair(diskId, this)); + workerQueue = DiskerQueue::Attach(dbName, KidIdentifier - 1); + const bool inserted = + IpcIoFiles.insert(std::make_pair(diskId, this)).second; + Must(inserted); } else { error_ = true; debugs(79,1, HERE << "error: no disker claimed " << dbName); @@ -251,7 +258,7 @@ IpcIoFile::ioInProgress() const void IpcIoFile::trackPendingRequest(IpcIoPendingRequest &pending) { - newerRequests->insert(std::make_pair(pending.id, &pending)); + newerRequests->insert(std::make_pair(lastRequestId, &pending)); if (!timeoutCheckScheduled) scheduleTimeoutCheck(); } @@ -266,7 +273,7 @@ IpcIoFile::push(IpcIoPendingRequest &pending) Must(pending.readRequest || pending.writeRequest); IpcIoMsg ipcIo; - ipcIo.requestId = pending.id; + ipcIo.requestId = lastRequestId; if (pending.readRequest) { ipcIo.command = IpcIo::cmdRead; ipcIo.offset = pending.readRequest->offset; @@ -297,8 +304,18 @@ void IpcIoFile::HandleOpenResponse(const Ipc::StrandSearchResponse &response) { debugs(47, 7, HERE << "coordinator response to open request"); - Must(response.data); - reinterpret_cast(response.data)->openCompleted(&response); + for (IpcIoFileList::iterator i = WaitingForOpen.begin(); + i != WaitingForOpen.end(); ++i) { + if (response.strand.tag == (*i)->dbName) { + (*i)->openCompleted(&response); + WaitingForOpen.erase(i); + return; + } + } + + debugs(47, 4, HERE << "LATE disker response to open for " << + response.strand.tag); + // nothing we can do about it; completeIo() has been called already } void @@ -346,18 +363,40 @@ IpcIoFile::HandleNotification(const Ipc::TypedMsgHdr &msg) DiskerHandleRequests(); else { const int diskId = msg.getInt(); - const IpcIoFiles::const_iterator i = ipcIoFiles.find(diskId); - Must(i != ipcIoFiles.end()); // XXX: warn and continue? + const IpcIoFilesMap::const_iterator i = IpcIoFiles.find(diskId); + Must(i != IpcIoFiles.end()); // XXX: warn and continue? i->second->handleResponses(); } } +/// handles open request timeout +void +IpcIoFile::OpenTimeout(void *const param) +{ + Must(param); + // the pointer is used for comparison only and not dereferenced + const IpcIoFile *const ipcIoFile = + reinterpret_cast(param); + for (IpcIoFileList::iterator i = WaitingForOpen.begin(); + i != WaitingForOpen.end(); ++i) { + if (*i == ipcIoFile) { + (*i)->openCompleted(NULL); + WaitingForOpen.erase(i); + break; + } + } +} + /// IpcIoFile::checkTimeouts wrapper void IpcIoFile::CheckTimeouts(void *const param) { Must(param); - reinterpret_cast(param)->checkTimeouts(); + const int diskId = reinterpret_cast(param); + debugs(47, 7, HERE << "diskId=" << diskId); + const IpcIoFilesMap::const_iterator i = IpcIoFiles.find(diskId); + if (i != IpcIoFiles.end()) + i->second->checkTimeouts(); } void @@ -370,9 +409,9 @@ IpcIoFile::checkTimeouts() for (RMCI i = olderRequests->begin(); i != olderRequests->end(); ++i) { IpcIoPendingRequest *const pending = i->second; - const int requestId = i->first; + const unsigned int requestId = i->first; debugs(47, 7, HERE << "disker timeout; ipcIo" << - KidIdentifier << '.' << diskId << '.' << requestId); + KidIdentifier << '.' << requestId); pending->completeIo(NULL); // no response delete pending; // XXX: leaking if throwing @@ -388,10 +427,9 @@ IpcIoFile::checkTimeouts() void IpcIoFile::scheduleTimeoutCheck() { - // we check all older requests at once so some may be wait for 2*timeout - const double timeout = 7; // in seconds + // we check all older requests at once so some may be wait for 2*Timeout eventAdd("IpcIoFile::CheckTimeouts", &IpcIoFile::CheckTimeouts, - this, timeout, 0, false); + reinterpret_cast(diskId), Timeout, 0, false); timeoutCheckScheduled = true; } @@ -440,16 +478,14 @@ IpcIoMsg::IpcIoMsg(): IpcIoPendingRequest::IpcIoPendingRequest(const IpcIoFile::Pointer &aFile): file(aFile), readRequest(NULL), writeRequest(NULL) { + Must(file != NULL); if (++file->lastRequestId == 0) // don't use zero value as requestId ++file->lastRequestId; - id = file->lastRequestId; } void IpcIoPendingRequest::completeIo(const IpcIoMsg *const response) { - Must(file != NULL); - if (readRequest) file->readCompleted(readRequest, response); else diff --git a/src/DiskIO/IpcIo/IpcIoFile.h b/src/DiskIO/IpcIo/IpcIoFile.h index 99573a22ba..c92833396e 100644 --- a/src/DiskIO/IpcIo/IpcIoFile.h +++ b/src/DiskIO/IpcIo/IpcIoFile.h @@ -7,6 +7,7 @@ #include "DiskIO/IORequestor.h" #include "ipc/forward.h" #include "ipc/Queue.h" +#include #include // TODO: expand to all classes @@ -79,6 +80,7 @@ private: static void Notify(const int peerId); + static void OpenTimeout(void *const param); static void CheckTimeouts(void *const param); void checkTimeouts(); void scheduleTimeoutCheck(); @@ -90,8 +92,8 @@ private: static void DiskerHandleRequest(const int workerId, IpcIoMsg &ipcIo); private: - typedef FewToOneBiQueue DiskerQueue; - typedef OneToOneBiQueue WorkerQueue; + typedef FewToOneBiQueue DiskerQueue; + typedef OneToOneBiQueue WorkerQueue; const String dbName; ///< the name of the file we are managing int diskId; ///< the process ID of the disker we talk to @@ -111,8 +113,14 @@ private: RequestMap *newerRequests; ///< newer requests (map2 or map1) bool timeoutCheckScheduled; ///< we expect a CheckTimeouts() call - typedef std::map IpcIoFiles; ///< maps diskerId to IpcIoFile - static IpcIoFiles ipcIoFiles; + static const double Timeout; ///< timeout value in seconds + + typedef std::list IpcIoFileList; + static IpcIoFileList WaitingForOpen; ///< pending open requests + + ///< maps diskerId to IpcIoFile, cleared in destructor + typedef std::map IpcIoFilesMap; + static IpcIoFilesMap IpcIoFiles; CBDATA_CLASS2(IpcIoFile); }; @@ -128,8 +136,7 @@ public: void completeIo(const IpcIoMsg *const response); public: - IpcIoFile::Pointer file; ///< the file object waiting for the response - unsigned int id; ///< request id + const IpcIoFile::Pointer file; ///< the file object waiting for the response ReadRequest *readRequest; ///< set if this is a read requests WriteRequest *writeRequest; ///< set if this is a write request diff --git a/src/Makefile.am b/src/Makefile.am index 2300eba3b4..89d76e77a4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -536,6 +536,7 @@ nodist_squid_SOURCES = \ $(BUILT_SOURCES) squid_LDADD = \ + $(DISK_LIBS) \ $(COMMON_LIBS) \ comm/libcomm.la \ eui/libeui.la \ @@ -544,7 +545,6 @@ squid_LDADD = \ $(XTRA_OBJS) \ $(DISK_LINKOBJS) \ $(REPL_OBJS) \ - $(DISK_LIBS) \ $(DISK_OS_LIBS) \ $(CRYPTLIB) \ $(REGEXLIB) \ diff --git a/src/ipc/Coordinator.cc b/src/ipc/Coordinator.cc index 4c6a40638d..8479da88be 100644 --- a/src/ipc/Coordinator.cc +++ b/src/ipc/Coordinator.cc @@ -185,7 +185,7 @@ Ipc::Coordinator::notifySearcher(const Ipc::StrandSearchRequest &request, { debugs(54, 3, HERE << "tell kid" << request.requestorId << " that " << request.tag << " is kid" << strand.kidId); - const StrandSearchResponse response(request.data, strand); + const StrandSearchResponse response(strand); TypedMsgHdr message; response.pack(message); SendMessage(MakeAddr(strandAddrPfx, request.requestorId), message); diff --git a/src/ipc/Makefile.am b/src/ipc/Makefile.am index eaa3d491a9..da53ddb4f0 100644 --- a/src/ipc/Makefile.am +++ b/src/ipc/Makefile.am @@ -12,6 +12,7 @@ libipc_la_SOURCES = \ Kids.cc \ Kids.h \ Messages.h \ + Queue.cc \ Queue.h \ StartListening.cc \ StartListening.h \ diff --git a/src/ipc/Queue.h b/src/ipc/Queue.h index 8c5554d66b..25839851b6 100644 --- a/src/ipc/Queue.h +++ b/src/ipc/Queue.h @@ -9,57 +9,68 @@ #include "Array.h" #include "ipc/AtomicWord.h" #include "ipc/SharedMemory.h" -#include "SquidString.h" #include "util.h" -/// Lockless fixed-capacity queue for a single writer and a single -/// reader. Does not manage shared memory segment. -template +class String; + +/// Lockless fixed-capacity queue for a single writer and a single reader. class OneToOneUniQueue { public: - OneToOneUniQueue(const int aCapacity); + class ItemTooLarge {}; + + OneToOneUniQueue(const String &id, const unsigned int maxItemSize, const int capacity); + OneToOneUniQueue(const String &id); - int size() const { return theSize; } - int capacity() const { return theCapacity; } + unsigned int maxItemSize() const { return shared->theMaxItemSize; } + int size() const { return shared->theSize; } + int capacity() const { return shared->theCapacity; } - bool empty() const { return !theSize; } - bool full() const { return theSize == theCapacity; } + bool empty() const { return !shared->theSize; } + bool full() const { return shared->theSize == shared->theCapacity; } + static int Bytes2Items(const unsigned int maxItemSize, int size); + static int Items2Bytes(const unsigned int maxItemSize, const int size); + + template bool pop(Value &value); ///< returns false iff the queue is empty + template bool push(const Value &value); ///< returns false iff the queue is full - static int Bytes2Items(int size); - static int Items2Bytes(const int size); - private: - unsigned int theIn; ///< input index, used only in push() - unsigned int theOut; ///< output index, used only in pop() + struct Shared { + Shared(const unsigned int aMaxItemSize, const int aCapacity); - AtomicWord theSize; ///< number of items in the queue - const int theCapacity; ///< maximum number of items, i.e. theBuffer size - Value theBuffer[]; + unsigned int theIn; ///< input index, used only in push() + unsigned int theOut; ///< output index, used only in pop() + + AtomicWord theSize; ///< number of items in the queue + const unsigned int theMaxItemSize; ///< maximum item size + const int theCapacity; ///< maximum number of items, i.e. theBuffer size + + char theBuffer[]; + }; + + SharedMemory shm; ///< shared memory segment + Shared *shared; ///< pointer to shared memory }; /// Lockless fixed-capacity bidirectional queue for two processes. -/// Manages shared memory segment. -template class OneToOneBiQueue { public: - typedef OneToOneUniQueue UniQueue; - /// Create a new shared queue. - OneToOneBiQueue(const char *const id, const int aCapacity); - OneToOneBiQueue(const char *const id); ///< Attach to existing shared queue. + OneToOneBiQueue(const String &id, const unsigned int maxItemSize, const int capacity); + OneToOneBiQueue(const String &id); ///< Attach to existing shared queue. int pushedSize() const { return pushQueue->size(); } + template bool pop(Value &value) { return popQueue->pop(value); } + template bool push(const Value &value) { return pushQueue->push(value); } private: - SharedMemory shm; ///< shared memory segment - UniQueue *popQueue; ///< queue to pop from for this process - UniQueue *pushQueue; ///< queue to push to for this process + OneToOneUniQueue *const popQueue; ///< queue to pop from for this process + OneToOneUniQueue *const pushQueue; ///< queue to push to for this process }; /** @@ -67,158 +78,73 @@ private: * pricesses. Implements a star topology: Many worker processes * communicate with the one central process. The central process uses * FewToOneBiQueue object, while workers use OneToOneBiQueue objects - * created with the Attach() method. Each worker has a unique integer ID - * in [0, workerCount) range. + * created with the Attach() method. Each worker has a unique integer + * ID in [0, workerCount) range. */ -template class FewToOneBiQueue { public: - typedef OneToOneBiQueue BiQueue; - - FewToOneBiQueue(const char *const id, const int aWorkerCount, const int aCapacity); - static BiQueue *Attach(const char *const id, const int workerId); + FewToOneBiQueue(const String &id, const int aWorkerCount, const unsigned int maxItemSize, const int capacity); + static OneToOneBiQueue *Attach(const String &id, const int workerId); ~FewToOneBiQueue(); bool validWorkerId(const int workerId) const; int workerCount() const { return theWorkerCount; } int pushedSize(const int workerId) const; + template bool pop(int &workerId, Value &value); ///< returns false iff the queue is empty + template bool push(const int workerId, const Value &value); ///< returns false iff the queue is full private: - static String BiQueueId(String id, const int workerId); - int theLastPopWorkerId; ///< the last worker ID we pop()ed from - Vector biQueues; ///< worker queues + Vector biQueues; ///< worker queues const int theWorkerCount; ///< number of worker processes - const int theCapacity; ///< per-worker capacity }; // OneToOneUniQueue -template -OneToOneUniQueue::OneToOneUniQueue(const int aCapacity): - theIn(0), theOut(0), theSize(0), theCapacity(aCapacity) -{ - assert(theCapacity > 0); -} - template bool -OneToOneUniQueue::pop(Value &value) +OneToOneUniQueue::pop(Value &value) { + if (sizeof(value) > shared->theMaxItemSize) + throw ItemTooLarge(); + if (empty()) return false; - const unsigned int pos = theOut++ % theCapacity; - value = theBuffer[pos]; - --theSize; + const unsigned int pos = + shared->theOut++ % shared->theCapacity * shared->theMaxItemSize; + memcpy(&value, shared->theBuffer + pos, sizeof(value)); + --shared->theSize; return true; } template bool -OneToOneUniQueue::push(const Value &value) +OneToOneUniQueue::push(const Value &value) { + if (sizeof(value) > shared->theMaxItemSize) + throw ItemTooLarge(); + if (full()) return false; - const unsigned int pos = theIn++ % theCapacity; - theBuffer[pos] = value; - ++theSize; + const unsigned int pos = + shared->theIn++ % shared->theCapacity * shared->theMaxItemSize; + memcpy(shared->theBuffer + pos, &value, sizeof(value)); + ++shared->theSize; return true; } -template -int -OneToOneUniQueue::Bytes2Items(int size) -{ - assert(size >= 0); - size -= sizeof(OneToOneUniQueue); - return size >= 0 ? size / sizeof(Value) : 0; -} - -template -int -OneToOneUniQueue::Items2Bytes(const int size) -{ - assert(size >= 0); - return sizeof(OneToOneUniQueue) + sizeof(Value) * size; -} - - -// OneToOneBiQueue - -template -OneToOneBiQueue::OneToOneBiQueue(const char *const id, const int capacity) : - shm(id) -{ - const int uniSize = UniQueue::Items2Bytes(capacity); - shm.create(uniSize * 2); - char *const mem = reinterpret_cast(shm.mem()); - assert(mem); - popQueue = new (mem) UniQueue(capacity); - pushQueue = new (mem + uniSize) UniQueue(capacity); -} - -template -OneToOneBiQueue::OneToOneBiQueue(const char *const id) : - shm(id) -{ - shm.open(); - char *const mem = reinterpret_cast(shm.mem()); - assert(mem); - pushQueue = reinterpret_cast(mem); - const int uniSize = pushQueue->Items2Bytes(pushQueue->capacity()); - popQueue = reinterpret_cast(mem + uniSize); -} // FewToOneBiQueue -template -FewToOneBiQueue::FewToOneBiQueue(const char *const id, const int aWorkerCount, const int aCapacity): - theLastPopWorkerId(-1), theWorkerCount(aWorkerCount), - theCapacity(aCapacity) -{ - biQueues.reserve(theWorkerCount); - for (int i = 0; i < theWorkerCount; ++i) { - const String biQueueId = BiQueueId(id, i); - biQueues.push_back(new BiQueue(biQueueId.termedBuf(), theCapacity)); - } -} - -template -typename FewToOneBiQueue::BiQueue * -FewToOneBiQueue::Attach(const char *const id, const int workerId) -{ - return new BiQueue(BiQueueId(id, workerId).termedBuf()); -} - -template -FewToOneBiQueue::~FewToOneBiQueue() -{ - for (int i = 0; i < theWorkerCount; ++i) - delete biQueues[i]; -} - -template -bool FewToOneBiQueue::validWorkerId(const int workerId) const -{ - return 0 <= workerId && workerId < theWorkerCount; -} - -template -int FewToOneBiQueue::pushedSize(const int workerId) const -{ - assert(validWorkerId(workerId)); - return biQueues[workerId]->pushedSize(); -} - template bool -FewToOneBiQueue::pop(int &workerId, Value &value) +FewToOneBiQueue::pop(int &workerId, Value &value) { ++theLastPopWorkerId; for (int i = 0; i < theWorkerCount; ++i) { @@ -233,19 +159,10 @@ FewToOneBiQueue::pop(int &workerId, Value &value) template bool -FewToOneBiQueue::push(const int workerId, const Value &value) +FewToOneBiQueue::push(const int workerId, const Value &value) { assert(validWorkerId(workerId)); return biQueues[workerId]->push(value); } -template -String -FewToOneBiQueue::BiQueueId(String id, const int workerId) -{ - id.append("__"); - id.append(xitoa(workerId)); - return id; -} - #endif // SQUID_IPC_QUEUE_H diff --git a/src/ipc/StrandSearch.cc b/src/ipc/StrandSearch.cc index b43690bd2e..a5ff3c8afd 100644 --- a/src/ipc/StrandSearch.cc +++ b/src/ipc/StrandSearch.cc @@ -12,16 +12,15 @@ #include "ipc/TypedMsgHdr.h" -Ipc::StrandSearchRequest::StrandSearchRequest(): requestorId(-1), data(0) +Ipc::StrandSearchRequest::StrandSearchRequest(): requestorId(-1) { } Ipc::StrandSearchRequest::StrandSearchRequest(const TypedMsgHdr &hdrMsg): - requestorId(-1), data(NULL) + requestorId(-1) { hdrMsg.checkType(mtStrandSearchRequest); hdrMsg.getPod(requestorId); - hdrMsg.getPod(data); hdrMsg.getString(tag); } @@ -29,30 +28,25 @@ void Ipc::StrandSearchRequest::pack(TypedMsgHdr &hdrMsg) const { hdrMsg.setType(mtStrandSearchRequest); hdrMsg.putPod(requestorId); - hdrMsg.putPod(data); hdrMsg.putString(tag); } /* StrandSearchResponse */ -Ipc::StrandSearchResponse::StrandSearchResponse(void *const aData, - const Ipc::StrandCoord &aStrand): - data(aData), strand(aStrand) +Ipc::StrandSearchResponse::StrandSearchResponse(const Ipc::StrandCoord &aStrand): + strand(aStrand) { } -Ipc::StrandSearchResponse::StrandSearchResponse(const TypedMsgHdr &hdrMsg): - data(NULL) +Ipc::StrandSearchResponse::StrandSearchResponse(const TypedMsgHdr &hdrMsg) { hdrMsg.checkType(mtStrandSearchResponse); - hdrMsg.getPod(data); strand.unpack(hdrMsg); } void Ipc::StrandSearchResponse::pack(TypedMsgHdr &hdrMsg) const { hdrMsg.setType(mtStrandSearchResponse); - hdrMsg.putPod(data); strand.pack(hdrMsg); } diff --git a/src/ipc/StrandSearch.h b/src/ipc/StrandSearch.h index 2bd1d81e79..913f661987 100644 --- a/src/ipc/StrandSearch.h +++ b/src/ipc/StrandSearch.h @@ -24,7 +24,6 @@ public: public: int requestorId; ///< sender-provided return address - void *data; ///< sender-provided opaque pointer String tag; ///< set when looking for a matching StrandCoord::tag }; @@ -32,12 +31,11 @@ public: class StrandSearchResponse { public: - StrandSearchResponse(void *const aData, const StrandCoord &strand); + StrandSearchResponse(const StrandCoord &strand); explicit StrandSearchResponse(const TypedMsgHdr &hdrMsg); ///< from recvmsg() void pack(TypedMsgHdr &hdrMsg) const; ///< prepare for sendmsg() public: - void *data; ///< a copy of the StrandSearchRequest::data StrandCoord strand; ///< answer matching StrandSearchRequest criteria };