Skip to content
This repository has been archived by the owner on Apr 24, 2020. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #6 from wavii/clean_streams
Clean streams
  • Loading branch information
erikfrey committed Aug 16, 2012
2 parents df419f3 + d627460 commit 9b7be61
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 357 deletions.
43 changes: 37 additions & 6 deletions include/darner/net/handler.h
@@ -1,9 +1,10 @@
#ifndef __DARNER_HANDLER_HPP__
#define __DARNER_HANDLER_HPP__

#include <sstream>

#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/optional.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>

Expand Down Expand Up @@ -76,10 +77,40 @@ class handler : public boost::enable_shared_from_this<handler>

// utils

// call done after a successful call or a failure. on fail, ensures the handler cleans up
void done(bool success, const std::string& msg = "");
void end(const char* msg = "END\r\n")
{
boost::asio::async_write(
socket_, boost::asio::buffer(msg), boost::bind(&handler::read_request, shared_from_this(), _1, _2));
}

void error(const char* msg, const char* error_type = "ERROR")
{
std::ostringstream oss;
oss << error_type << ' ' << msg << "\r\n";
buf_ = oss.str();

boost::asio::async_write(
socket_, boost::asio::buffer(buf_), boost::bind(&handler::hang_up, shared_from_this(), _1, _2));
}

void error(const char* location, const boost::system::error_code& e)
{
log::ERROR("handler<%1%>::%2%: %3%", shared_from_this(), location, e.message());
}

void error(const char* location, const boost::system::system_error& ex, bool echo = true)
{
log::ERROR("handler<%1%>::%2%: %3%", shared_from_this(), location, ex.code().message());

if (echo)
{
buf_ = "SERVER_ERROR " + ex.code().message() + "\r\n";
boost::asio::async_write(
socket_, boost::asio::buffer(buf_), boost::bind(&handler::hang_up, shared_from_this(), _1, _2));
}
}

void finalize(const boost::system::error_code& e, size_t bytes_transferred);
void hang_up(const boost::system::error_code& e, size_t bytes_transferred) {}

const queue::size_type chunk_size_;

Expand All @@ -92,8 +123,8 @@ class handler : public boost::enable_shared_from_this<handler>
std::string buf_;
request req_;

boost::optional<iqstream> pop_stream_;
boost::optional<oqstream> push_stream_;
iqstream pop_stream_;
oqstream push_stream_;
};

} // darner
Expand Down
32 changes: 21 additions & 11 deletions include/darner/queue/iqstream.h
@@ -1,7 +1,7 @@
#ifndef __DARNER_QUEUE_IQSTREAM_H__
#define __DARNER_QUEUE_IQSTREAM_H__

#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>

#include "darner/queue/queue.h"

Expand All @@ -12,20 +12,25 @@ class iqstream
public:

/*
* tries to open an item immediately. reads will fail if it couldn't open an item.
* destroying an open iqstream will close it with erase = false
*/
iqstream(queue& _queue);
~iqstream();

/*
* on the first read, tries to fetch an item and returns true if one was available
* tries to open an item for reading. returns true if an item was available.
*/
bool open(boost::shared_ptr<queue> queue);

/*
* reads a chunk
* can continue calling read until eof (until tell() == size()).
*/
bool read(std::string& result);
void read(std::string& result);

/*
* closes the iqstream. if remove, completes the pop of the item off the queue, otherwise returns it
* closes the iqstream. if erase, completes the pop of the item off the queue, otherwise returns it.
*/
void close(bool remove);
void close(bool erase);

/*
* returns the position in the stream in bytes. only valid after first read()
Expand All @@ -35,14 +40,19 @@ class iqstream
/*
* returns the size of the item. only valid after first read()
*/
queue::size_type size() const { return header_ ? header_->size : tell_; }
queue::size_type size() const { return header_.size; }

/*
* returns true if open
*/
operator bool() const { return queue_; }

private:

queue& queue_;
boost::shared_ptr<queue> queue_;

boost::optional<queue::id_type> id_; // id of key in queue, only set after a succesful first read
boost::optional<queue::header_type> header_; // only set if it's multi-chunk
queue::id_type id_; // id of key in queue, only valid if open() succeeded
queue::header_type header_; // only valid if it's multi-chunk
queue::size_type chunk_pos_;
queue::size_type tell_;
};
Expand Down
28 changes: 17 additions & 11 deletions include/darner/queue/oqstream.h
@@ -1,8 +1,7 @@
#ifndef __DARNER_QUEUE_OQSTREAM_H__
#define __DARNER_QUEUE_OQSTREAM_H__

#include <boost/optional.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>

#include "darner/queue/queue.h"

Expand All @@ -12,32 +11,39 @@ class oqstream
{
public:

oqstream(queue& _queue, queue::size_type chunks);
/*
* destroying an unfinished oqstream will cancel it
*/
~oqstream();

/*
* immediately opens an oqstream for writing. the stream will automatically close after chunks_count chunks
* have been written
*/
void open(boost::shared_ptr<queue> queue, queue::size_type chunks_count);

/*
* writes a chunk of the item. fails if more chunks are written than originally reserved.
*/
void write(const std::string& value);
void write(const std::string& chunk);

/*
* cancels the oqstream write. only available to mutli-chunks that haven't written all their chunks yet.
* cancels the oqstream write. only available if the stream hasn't written chunks_count chunks yet
*/
void cancel();

/*
* returns the position in the stream in bytes.
*/
queue::size_type tell() const { return tell_; }
queue::size_type tell() const { return header_.size; }

private:

queue& queue_;
queue::size_type chunks_;
boost::shared_ptr<queue> queue_;

boost::optional<queue::id_type> id_; // id of key in queue, only set after all chunks are written
boost::optional<queue::header_type> header_; // only set if it's multi-chunk
queue::id_type id_; // id of key in queue, only set after all chunks are written
queue::header_type header_; // only set if it's multi-chunk
queue::size_type chunk_pos_;
queue::size_type tell_;
};

} // darner
Expand Down
81 changes: 54 additions & 27 deletions include/darner/queue/queue.h
Expand Up @@ -8,7 +8,6 @@
#include <boost/array.hpp>
#include <boost/ptr_container/ptr_list.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/optional.hpp>
#include <boost/asio.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
Expand All @@ -22,27 +21,28 @@ namespace darner {
* queue is a fifo queue that is O(log(queue size / cache size)) for pushing/popping. it boasts these features:
*
* - an evented wait semantic for queue poppers
* - items are first checked out, then later deleted or returned back into the queue
* - popping is two-phase with a begin and an end. ending a pop can either erase it or return it back to the queue.
* - large items are streamed in a chunk at a time
*
* queue will post events such as journal writes and waits to a provided boost::asio io_service. interrupting the
* io_service with pending events is okay - queue is never in an inconsistent state between io events.
* all queue methods are synchronous except for wait(), which starts an async timer on the provided io_service.
*
* queue is not thread-safe, it assumes a single-thread calling and operating the provided io_service
*/
class queue
{
public:

// a queue can only ever have a backlog of 2^64 items. so at darner's current peak throughput you can only run the
// server for 23 million years :(
typedef boost::uint64_t id_type;
typedef boost::uint64_t size_type;
typedef boost::function<void (const boost::system::error_code& error)> success_callback;
typedef boost::function<void (const boost::system::error_code& error)> wait_callback;

// open or create the queue at the path
queue(boost::asio::io_service& ios, const std::string& path);

// wait up to wait_ms milliseconds for an item to become available, then call cb with success or timeout
void wait(size_type wait_ms, const success_callback& cb);
void wait(size_type wait_ms, const wait_callback& cb);

// returns the number of items in the queue
size_type count() const;
Expand All @@ -60,7 +60,7 @@ class queue
{
public:

header_type() : beg(0), end(0), size(0) {}
header_type() : beg(0), end(1), size(0) {}
header_type(id_type _beg, id_type _end, size_type _size)
: beg(_beg), end(_end), size(_size) {}
header_type(const std::string& buf)
Expand All @@ -79,51 +79,58 @@ class queue
mutable std::string buf_;
};

// queue methods:
// queue methods aren't meant to be used directly. instead create an iqstream or oqstream to use it

/*
* pushes a value to to the queue. returns true for success, false if there was a problem writing to the journal
* pushes an item to to the queue.
*/
void push(boost::optional<id_type>& result, const std::string& value);
void push(id_type& result, const std::string& item);

/*
* pushes a header to to the queue. call this after inserting a range of data chunks.
* pushes a header to to the queue. a header points to a range of chunks in a multi-chunk item.
*/
void push(boost::optional<id_type>& result, const header_type& value);
void push(id_type& result, const header_type& header);

/*
* begins the popping of an item. if the item is a single chunk, pops the value, otherwise just pops the
* header. will wait wait_ms milliseconds for an item to become available before either returning a succes
* or timeout status to the callback cb
* begins popping an item. if no items are available, immediately returns false. once an item pop is begun,
* it is owned solely by the caller, and must eventually be pop_ended. pop_begin is constant time.
*/
bool pop_open(boost::optional<id_type>& result_id, boost::optional<header_type>& result_header,
std::string& result_value);
bool pop_begin(id_type& result);

/*
* finishes the popping of an item. if remove = true, deletes the dang ol' item, otherwise returns it
* back into the queue
* once has a pop has begun, call pop_read. if the item is just one chunk (end - beg < 2), result_item will be
* immediately populated, otherwise fetch the chunks in the header's range [beg, end).
*/
void pop_close(bool remove, id_type id, const boost::optional<header_type>& header);
void pop_read(std::string& result_item, header_type& result_header, id_type id);

/*
* finishes the popping of an item. if erase = true, deletes the dang ol' item, otherwise returns it
* back to its position near the tail of the queue. closing an item with erase = true is constant time, but
* closing an item with erase = false could take logn time and linear memory for # returned items.
*
* the simplest way to address this is to limit the number of items that can be opened at once.
*/
void pop_end(bool erase, id_type id, const header_type& header);

// chunk methods:

/*
* returns a header with a range of reserved chunks
* returns to a header a range of reserved chunks
*/
void reserve_chunks(boost::optional<header_type>& result, size_type chunks);
void reserve_chunks(header_type& result, size_type count);

/*
* writes a chunk
*/
void write_chunk(const std::string& value, id_type chunk_key);
void write_chunk(const std::string& chunk, id_type chunk_key);

/*
* reads a chunk
*/
void read_chunk(std::string& result, id_type chunk_key);

/*
* removes all chunks referred to by a header
* removes all chunks referred to by a header. use this when aborting a multi-chunk push.
*/
void erase_chunks(const header_type& header);

Expand Down Expand Up @@ -157,13 +164,13 @@ class queue
// ties a set of results to a deadline timer
struct waiter
{
waiter(boost::asio::io_service& ios, size_type wait_ms, const success_callback& _cb)
waiter(boost::asio::io_service& ios, size_type wait_ms, const wait_callback& _cb)
: cb(_cb),
timer(ios, boost::posix_time::milliseconds(wait_ms))
{
}

success_callback cb;
wait_callback cb;
boost::asio::deadline_timer timer;
};

Expand All @@ -183,9 +190,29 @@ class queue
// any operation that mutates the queue or the waiter state should run this to crank any pending events
void spin_waiters();

// fires either if timer times out, or is canceled
// fires either if timer times out or is canceled
void waiter_timeout(const boost::system::error_code& e, boost::ptr_list<waiter>::iterator waiter_it);

// some leveldb sugar:

void put(const key_type& key, const std::string& value)
{
if (!journal_->Put(leveldb::WriteOptions(), key.slice(), value).ok())
throw boost::system::system_error(boost::system::errc::io_error, boost::system::system_category());
}

void get(const key_type& key, std::string& result)
{
if (!journal_->Get(leveldb::ReadOptions(), key.slice(), &result).ok())
throw boost::system::system_error(boost::system::errc::io_error, boost::system::system_category());
}

void write(leveldb::WriteBatch& batch)
{
if (!journal_->Write(leveldb::WriteOptions(), &batch).ok())
throw boost::system::system_error(boost::system::errc::io_error, boost::system::system_category());
}

boost::scoped_ptr<comparator> cmp_;
boost::scoped_ptr<leveldb::DB> journal_;

Expand Down

0 comments on commit 9b7be61

Please sign in to comment.