ジャーナリング付き入出力クラス (その4)

このエントリで、 ioblock_type の備忘録は終わりです。

ioblock_type はデフォルト・コンストラクタだけで、 それでメンバを初期化します。

#include <map>
#include <string>
#include <utility>
#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

ioblock_type::ioblock_type ()
    : m_fail (false),
      m_file_name (), m_log_name (), m_mode (),
      m_desc (-1), m_log (-1),
      m_block_size (0), m_cache_limit (524288U), m_dirty_limit (262144U),
      m_orig_size (0), m_file_size (0), m_save_point (0),
      m_block (), m_dirty (), m_ring (nullptr), m_free (nullptr)
{
}

ファイルをオープンし、 バッファ・キャッシュを作るのは data_open メンバ関数の役目です。 今のところ、 オープン時に指定できるアクセス・モードは、 読み取り専用、 読み書き、 ファイルを初期化して読み書きの 3 通りです。 fopen システム・コールのアクセス・モードに類似するやりかたでモードを指定します。 データをオープンする方式では、 今回は一番単純なやりかたにしてあり、 オープンからクローズまでの間、 ファイルをずっとジャイアント・ロックします。 読み取り専用でオープンするときは共用ロック、 読み書きでオープンすると排他ロックをおこないます。 今の実装では共用ロックも排他ロックも、 どちらもブロッキング・ロックで、 ロックを獲得できるまでプロセスを待ち状態にします。 ノンブロッキングにしていない理由は、 単純なノンブロッキングにするぐらいなら MultiVersion Concurrency Control (MVCC)にする方が有用だからと考えたからです。 MVCC が必要ないならブロッキング・ロックで十分でしょう。 このクラスは追記型のログ先行書き込みにしているので、 プロセス間でアクティブ・シーケンス集合を共用する仕掛けを追加し、 トランザクションが 2 つ以上動いているときに data_flushredo をスキップすることで MVCC にできるはずですけど、 それでもマルチ・プロセスで MVCC にするメリットはないように思えます。 マルチ・スレッドの一つのプロセスで MVCC を実装するならジャイアント・ロックで良いでしょうし。

細かな点としては、 ファイル・オープン時にジャーナリング・ファイルがあっても、 この段階では redo しません。 さらに、 ブロック・サイズに 256 未満の値を指定しておくと、 オープン時に fstat システムで取得したブロック・サイズに設定しなおします。

// ioblock.data_open ("example.data", "r");    読み取り専用、 共用ロック、 ファイルがないときはエラー
// ioblock.data_open ("example.data", "r+");   読み書き、     排他ロック、 ファイルがないときは作成
// ioblock.data_open ("example.data", "w");    読み書き、     排他ロック、 ファイルがないときは作成、 あるときはファイルを空にする
// ioblock.data_open ("example.data", "w+");   "w" と同じ
bool
ioblock_type::data_open (std::string const& name, std::string const& mode)
{
    m_fail = false;
    if (m_desc >= 0 || name.empty ()) {
        return set_fail ();
    }
    if (mode != "r" && mode != "r+" && mode != "w" && mode != "w+") {
        return set_fail ();
    }
    m_file_name = name;
    m_log_name = name + ",j";
    m_mode = mode;
    struct flock lk;
    lk.l_type = mode == "r" ? F_RDLCK : F_WRLCK;
    lk.l_whence = SEEK_SET;
    lk.l_start = lk.l_len = 0;
    lk.l_pid = getpid ();
    struct stat st;
    bool log_exist = stat (m_log_name.c_str (), &st) == 0;
    m_save_point = mode[0] == 'r' && log_exist ? st.st_size : 0;
    int const open_flags = mode == "r"  ? O_RDONLY
                         : mode == "r+" ? O_RDWR|O_CREAT
                         :                O_RDWR|O_CREAT|O_TRUNC;
    m_desc = open (m_file_name.c_str (), open_flags, 0600);
    m_log = -1;
    if (m_desc < 0 || fstat (m_desc, &st) < 0) {
        return set_fail ();
    }
    if (fcntl (m_desc, F_SETLKW, &lk) < 0) {
        return set_fail ();
    }
    m_orig_size = st.st_size;
    m_file_size = m_orig_size;
    if (m_block_size < 256U) {
        m_block_size = st.st_blksize;
    }
    std::size_t const block_size_advisary = m_block_size;
    m_block_size = 1U;
    while (m_block_size < block_size_advisary) {
        m_block_size <<= 1;
    }
    if (mode != "r" || log_exist) {
        m_log = open (m_log_name.c_str (), open_flags, 0600);
    }
    cache_init (m_cache_limit / m_block_size);
    if (m_save_point > 0) {
        reload_head ();
    }
    return true;
}

残るメンバ関数は、 パラメータの設定です。 パラメータは 3 つあります。 バッファ・キャッシュの容量を set_cache_limit、 書き込み済みブロックのキャッシュ容量を set_dirty_limit、 ブロック・サイズを set_block_size で設定します。 これらはファイルをオープンしている間は設定しようとしても無視します。

void
ioblock_type::set_cache_limit (std::size_t bytes)
{
    if (! is_open ()) {
        m_cache_limit = bytes;
    }
}

void
ioblock_type::set_dirty_limit (std::size_t bytes)
{
    if (! is_open ()) {
        m_dirty_limit = bytes;
    }
}

void
ioblock_type::set_block_size (std::size_t bytes)
{
    if (! is_open ()) {
        m_block_size = bytes;
    }
}