/** * \fn static void* threadStart(void* handle) * \brief start the filter thread * \param handle the stream to use the filters on */ static void* threadStart(void* handle) { BufferedWriter* self = (BufferedWriter*)handle; BufferChain* chain = self->firstChain; while (self->active) { oml_lock(&self->lock, "bufferedWriter"); pthread_cond_wait(&self->semaphore, &self->lock); // Process all chains which have data in them while(1) { if (mbuf_message(chain->mbuf) > mbuf_rdptr(chain->mbuf)) { // got something to read from this chain while (!processChain(self, chain)); } // stop if we caught up to the writer if (chain == self->writerChain) break; chain = chain->next; } oml_unlock(&self->lock, "bufferedWriter"); } return NULL; }
/** Find the next empty write chunk, sets self->writerChunk to it and returns * it. * * We only use the next one if it is empty. If not, we essentially just filled * up the last chunk and wrapped around to the socket reader. In that case, we * either create a new chunk if the overall buffer can still grow, or we drop * the data from the current one. * * \warning A lock on the current writer chunk should be held prior to calling * this function. It will be released, and the returned writerChunk will be * similarly locked. * * \param self BufferedWriter pointer * \param current locked BufferChunk to use or from which to find the next * \return a locked BufferChunk in which data can be stored */ BufferChunk* getNextWriteChunk(BufferedWriter* self, BufferChunk* current) { int nlost; BufferChunk* nextBuffer; assert(current != NULL); nextBuffer = current->next; oml_unlock(¤t->lock, __FUNCTION__); assert(nextBuffer != NULL); oml_lock(&self->lock, __FUNCTION__); if (nextBuffer == self->nextReaderChunk) { if (self->unallocatedBuffers > 0) { /* The next buffer is the next to be read, but we can allocate more, * allocate a new buffer, insert it after the current writer, and use it */ nextBuffer = createBufferChunk(self); assert(nextBuffer); /** \todo Use existing buffer if allocation fails */ oml_unlock(&self->lock, __FUNCTION__); oml_lock(¤t->lock, __FUNCTION__); nextBuffer->next = current->next; current->next = nextBuffer; /* we have a lock on this one */ oml_unlock(¤t->lock, __FUNCTION__); oml_lock(&self->lock, __FUNCTION__); } else { /* The next buffer is the next to be read, and we cannot allocate more, * use it, dropping unread data, and advance the read pointer */ self->nextReaderChunk = nextBuffer->next; } } self->writerChunk = nextBuffer; nlost = bw_msgcount_reset(self); self->nlost += nlost; oml_unlock(&self->lock, __FUNCTION__); oml_lock(&nextBuffer->lock, __FUNCTION__); if (nlost) { logwarn("%s: Dropping %d samples (%dB)\n", self->outStream->dest, nlost, mbuf_fill(nextBuffer->mbuf)); } mbuf_clear2(nextBuffer->mbuf, 0); // Now we just need to copy the message from current to self->writerChunk int msgSize = mbuf_message_length(current->mbuf); if (msgSize > 0) { mbuf_write(nextBuffer->mbuf, mbuf_message(current->mbuf), msgSize); mbuf_reset_write(current->mbuf); } return nextBuffer; }
/** Finalise a marshalled message. * * Depending on the number of values packed, change the type of message, and * write the actual size in the right location of the header, in network byte * order. * * \param mbuf MBuffer where marshalled data is * \return 1 * \see marshal_init, marshal_measurements, marshal_values */ int marshal_finalize(MBuffer* mbuf) { uint8_t* buf = mbuf_message (mbuf); OmlBinMsgType type = marshal_get_msgtype (mbuf); size_t len = mbuf_message_length (mbuf); if (len > UINT32_MAX) { logwarn("Message length %d longer than maximum packet length (%d); " "packet will be truncated\n", len, UINT32_MAX); len = UINT32_MAX; } if (type == OMB_DATA_P && len > UINT16_MAX) { /* * We assumed a short packet, but there is too much data, so we * have to shift the whole buffer down by 2 bytes and convert to a * long packet. */ uint8_t s[2] = {0}; /* Put some padding in the buffer to make sure it has room, and maintains its invariants */ mbuf_write (mbuf, s, sizeof (s)); memmove (&buf[PACKET_HEADER_SIZE+2], &buf[PACKET_HEADER_SIZE], len - PACKET_HEADER_SIZE); len += 2; buf[2] = type = OMB_LDATA_P; } switch (type) { case OMB_DATA_P: len -= PACKET_HEADER_SIZE; // Data length minus header uint16_t nlen16 = htons (len); memcpy (&buf[3], &nlen16, sizeof (nlen16)); break; case OMB_LDATA_P: len -= PACKET_HEADER_SIZE + 2; // Data length minus header uint32_t nlen32 = htonl (len); // pure data length memcpy (&buf[3], &nlen32, sizeof (nlen32)); break; } return 1; }
/** Marshal the array of values into an MBuffer. * * Metadata of the measurement stream should already have been written * with marshal_measurements(). Each element of values is written with * marshal_value(). Finally, the number of elements in the message is * updated in its header, by incrementing the relevant field * (depending on its OmlBinMsgType) by value_count. * * If the returned number is negative, marshalling didn't finish as * the provided buffer was short of the number of bytes returned (when * multiplied by -1); the entire message has been reset (by * marshal_value()), and marshalling should restart with * marshal_init(), after the MBuffer has been adequately resized or * repacked. * * Once all data has been marshalled, marshal_finalize() should be * called to finish preparing the message. * * \param mbuf MBuffer to write marshalled data to * \param values array of OmlValue of length value_count * \param value_count length the values array * \return 1 on success, or -1 otherwise (marshalling should then restart from marshal_init()) * \see marshal_init, marshal_measurements, marshal_value, marshal_finalize, mbuf_repack_message, mbuf_repack_message2, mbuf_resize */ int marshal_values(MBuffer* mbuf, OmlValue* values, int value_count) { OmlValue* val = values; int i; for (i = 0; i < value_count; i++, val++) { if(!marshal_value(mbuf, oml_value_get_type(val), oml_value_get_value(val))) return -1; } uint8_t* buf = mbuf_message (mbuf); OmlBinMsgType type = marshal_get_msgtype (mbuf); switch (type) { case OMB_DATA_P: buf[5] += value_count; break; case OMB_LDATA_P: buf[7] += value_count; break; } return 1; }
/** Function called after all items in a tuple have been sent * \see oml_writer_row_end * * This releases the lock on the BufferedWriter. * * \see BufferedWriter, bw_unlock_buf, marshal_finalize */ static int owb_row_end(OmlWriter* writer, OmlMStream* ms) { (void)ms; OmlBinWriter* self = (OmlBinWriter*)writer; MBuffer* mbuf; if ((mbuf = self->mbuf) == NULL) { return 0; /* previous use of mbuf failed */ } marshal_finalize(self->mbuf); if (marshal_get_msgtype (self->mbuf) == OMB_LDATA_P) { self->msgtype = OMB_LDATA_P; // Generate long packets from now on. } if (0 == ms->index) { /* This is schema0, also push the data into the meta_buf * to be replayed after a disconnection. * * At the moment, the oml_outs_write_f takes header information as a * whole, but does not push more once it has sent the initial block. Its * two last parameters are only used to resend the entirety of the headers * when a disconnection does occur, nothing before. * * We therefore send the extra piece of data the normal way, but also * record it, separately, in the meta_buf * * XXX: This logic should be in higher layer levels, but given the current * implementation, with some of it already spread down into the * OmlOutStream (oml_outs_write_f), this require a much bigger refactoring. * It is also duplicated with the OmlTextWriter (see #1101). */ _bw_push_meta(self->bufferedWriter, mbuf_message(self->mbuf), mbuf_message_length(self->mbuf)); } mbuf_begin_write(mbuf); self->mbuf = NULL; bw_unlock_buf(self->bufferedWriter); return 1; }
// This function finds the next empty write chain, sets +self->writeChain+ to // it and returns. // // We only use the next one if it is empty. If not, we // essentially just filled up the last chain and wrapped // around to the socket reader. In that case, we either create a new chain // if the overall buffer can still grow, or we drop the data from the current one. // // This assumes that the current thread holds the +self->lock+ and the lock on // the +self->writeChain+. // BufferChain* getNextWriteChain( BufferedWriter* self, BufferChain* current ) { assert(current != NULL); BufferChain* nextBuffer = current->next; assert(nextBuffer != NULL); BufferChain* resChain = NULL; if (mbuf_remaining(nextBuffer->mbuf) == 0) { // It's empty, we can use it mbuf_clear2(nextBuffer->mbuf, 0); resChain = nextBuffer; } else if (self->chainsAvailable > 0) { // insert new chain between current and next one. BufferChain* newBuffer = createBufferChain(self); newBuffer->next = nextBuffer; current->next = newBuffer; resChain = newBuffer; } else { // Filled up buffer, time to drop data and reuse current buffer // Current buffer holds most recent added data (we drop from the queue's tail //assert(current->reading == 0); assert(current->reading == 0); o_log (O_LOG_WARN, "Dropping %d bytes of measurement data\n", mbuf_fill(current->mbuf)); mbuf_repack_message2(current->mbuf); return current; } // Now we just need to copy the +message+ from +current+ to +resChain+ int msgSize = mbuf_message_length(current->mbuf); if (msgSize > 0) { mbuf_write(resChain->mbuf, mbuf_message(current->mbuf), msgSize); mbuf_reset_write(current->mbuf); } return resChain; }
/** Send data contained in one chunk. * * \warning This function acquires the lock on the BufferedWriter for the time * it takes to check the double-buffer. * * \warning This function acquires the lock on the chunk being processed for * the time it takes to check it and swap the double buffer. * * \bug The meta buffer should also be protected. * * \param self BufferedWriter to process * \param chunk link of the chunk to process * * \return 1 if chunk has been fully sent, 0 if not, -1 on continuing back-off, -2 otherwise * \see oml_outs_write_f */ static int processChunk(BufferedWriter* self, BufferChunk* chunk) { time_t now; int ret = -2; ssize_t cnt = 0; MBuffer *read_buf = NULL; assert(self); assert(self->meta_buf); assert(self->read_buf); assert(chunk); assert(chunk->mbuf); oml_lock(&self->lock, __FUNCTION__); if (mbuf_message(self->read_buf) > mbuf_rdptr(self->read_buf)) { /* There is unread data in the double buffer */ read_buf = self->read_buf; } oml_unlock(&self->lock, __FUNCTION__); oml_lock(&chunk->lock, __FUNCTION__); if ((NULL == read_buf) && (mbuf_message(chunk->mbuf) >= mbuf_rdptr(chunk->mbuf))) { /* There is unread data in the read buffer, swap MBuffers */ read_buf = chunk->mbuf; chunk->mbuf = self->read_buf; } oml_unlock(&chunk->lock, __FUNCTION__); oml_lock(&self->lock, __FUNCTION__); self->read_buf = read_buf; oml_unlock(&self->lock, __FUNCTION__); if (NULL == read_buf) { /* The current message is not after the read pointer, * we must be on the writer chunk */ ret = 1; goto processChunk_cleanup; } time(&now); if (difftime(now, self->last_failure_time) < self->backoff) { logdebug("%s: Still in back-off period (%ds)\n", self->outStream->dest, self->backoff); ret = -1; goto processChunk_cleanup; } while (mbuf_write_offset(read_buf) > mbuf_read_offset(read_buf)) { oml_lock(&self->meta_lock, __FUNCTION__); cnt = self->outStream->write(self->outStream, mbuf_rdptr(read_buf), mbuf_message_offset(read_buf) - mbuf_read_offset(read_buf), mbuf_rdptr(self->meta_buf), mbuf_fill(self->meta_buf)); oml_unlock(&self->meta_lock, __FUNCTION__); if (cnt > 0) { mbuf_read_skip(read_buf, cnt); if (self->backoff) { self->backoff = 0; loginfo("%s: Connected\n", self->outStream->dest); } } else { self->last_failure_time = now; if (!self->backoff) { self->backoff = 1; } else if (self->backoff < UINT8_MAX) { self->backoff *= 2; } logwarn("%s: Error sending, backing off for %ds\n", self->outStream->dest, self->backoff); goto processChunk_cleanup; } } ret = 1; processChunk_cleanup: return ret; }
/** Retrieve the message type from an MBuffer containing a marshalling packet. * * \param mbuf MBuffer to read the mbuf marshalling header frow * \return the OmlBinMsgType of the packet */ OmlBinMsgType marshal_get_msgtype (MBuffer *mbuf) { return (OmlBinMsgType)(mbuf_message (mbuf))[2]; }