db4oでの排他制御

オブジェクトデータベースのdb4oが面白そうなので、ちまちま弄っています。データベースで一番重要なのは如何にデータを守るかなので、排他制御がとても気になります。と言うわけで簡単なクライアント/サーバーモジュールを作成して実験してみました。が、色々分からないことばかりで、しばらくは勉強&調査が必要そうです。(^^;
基本的にはチュートリアルを参考にしています。

  • model.dll データベースに格納するオブジェクト。
  • register.exe データベースを初期化する。
  • server.exe サーバー。
  • client.exe クライアント。ドライバーのポイントをカウントアップする。

以上、4つのアセンブリを作成します。

model.cs

using System;

public class Pilot {
    string _name;
    int _points;

    public Pilot (string name, int points) {
        _name = name;
        _points = points;
    }

    public string Name {
        get { return _name; }
        set { _name = value; }
    }

    public int Points {
        get { return _points; }
        set { _points = value; }
    }

    public void AddPoints (int points) {
        _points += points;
    }

    public override string ToString () {
        return String.Format ("{0}/{1}", _name, _points);
    }
}

public class Car {
    string _model;
    Pilot _pilot;

    public Car (string model) {
        _model = model;
    }

    public string Model {
        get { return _model; }
        set { _model = value; }
    }

    public Pilot Pilot {
        get { return _pilot; }
        set { _pilot = value; }
    }

    public override string ToString () {
        return String.Format ("{0}[{1}]", _model, _pilot);
    }
}

チュートリアルまんまですね。

register.cs

using System;
using System.IO;
using com.db4o;

public class App {
    const string DbName = "mydb";
    public static void Main () {
        if (File.Exists (DbName))
            File.Delete (DbName);

        Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnUpdate (true);
        Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnDelete (true);
        ObjectContainer db = null;
        try {

            db = Db4o.OpenFile ("mydb");

            Car car;

            car = new Car ("Ferrari");
            car.Pilot = new Pilot ("Michael Schumacher", 100);
            db.Set (car);

            car = new Car ("BMW");
            car.Pilot = new Pilot ("Rubens Barrichello", 99);
            db.Set (car);
        }
        finally {
            if (db != null)
                db.Close ();
        }
    }
}

データベースを作り直してデータを2件追加します。実験しているとデータベースがぐちゃぐちゃになるので、何かと便利。

server.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using com.db4o;
using com.db4o.messaging;

public class Server : MessageRecipient {
    const string file = "mydb";
    const string user = "user";
    const string pass = "pass";
    const string host = "localhost";
    const int port = 1234;
    private bool stop = false;

    public void Run () {
        lock (this) {
            ObjectServer server = Db4o.OpenServer (file, port);
            server.GrantAccess (user, pass);
            server.Ext().Configure().SetMessageRecipient (this);
            try {
                if (!stop) {
                    Monitor.Wait (this);
                }
            }
            catch (Exception e) {
                Console.WriteLine (e.Message);
                Console.WriteLine (e.StackTrace);
            }
            finally {
                server.Close ();
            }
        }
    }

    public void ProcessMessage (ObjectContainer conn, object message) {
        if ("Stop".Equals *1;
        foreach (Car car in result)
            Console.WriteLine (car);
    }
}

クライアントからの接続をひたすら待ちます。

client.cs

using System;
using System.Collections.Generic;
using com.db4o;
using com.db4o.messaging;

public class App {
    const string host = "localhost";
    const int port = 1234;
    const string user = "user";
    const string pass = "pass";
    public static void Main(string[] args) {
        ObjectContainer client = null;
        try {
            Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnUpdate (true);
            Db4o.Configure().ObjectClass(typeof(Car)).CascadeOnDelete (true);
            client = Db4o.OpenClient (host, port, user, pass);
            foreach (Pilot pilot in Query (client)) {
                pilot.AddPoints (1);
                client.Set (pilot);
            }

           Console.WriteLine ("Before Commit !");
           Console.ReadLine ();

           client.Commit ();

           Console.WriteLine ("Commit Done!");
           Console.ReadLine ();

           foreach (Pilot pilot in Query (client))
                Console.WriteLine (pilot);
        }

        finally {
            if (client != null)
                client.Close ();
            Console.WriteLine ("Closed.");
        }
        Console.WriteLine ("Exit.");
    }

    static IList<Pilot> Query(ObjectContainer client) {
        IList<Pilot> pilots = client.Query<Pilot>(
                delegate (Pilot pilot) { 
                /*
                return pilot.Points > 99 && pilot.Points < 199 ||
                pilot.Name == "Rubens Bariichello";*/
                return true;
                });
        return pilots;
    }
}

データベースへの変更を行います。コミット前とコミット後の2回、キー入力待ちを入れています。まず、初期データベースを作成します。

$ register.exe

で、サーバーの起動。

$ server.exe

クライアントの起動。

$ client.exe
Before Commit !

ここでキー入力を待ちます。このタイミングでは値を変更しているが、コミットは行っていない状態です。では、もう一つクライアントを起動してみましょう。

$ client.exe
Before Commit !

リレーショナルデータベースの感覚だと更新のタイミングでブロックされるのですが、構わず処理が進むようです。このまま両方のclientの処理を進めると後から実行した方が固まってしまいます。これを何度か繰り返すとデータファイルが壊れてしまいました。絶対使い方を間違っていそうだ・・・

*1:string)message) ) { Close (); } } public void Close () { lock (this) { stop = true; Monitor.PulseAll (this); } } } public class App { public static void Main(string[] args) { new Server ().Run (); } public static void ListResult (ObjectSet result) { Console.WriteLine (result.Size (