mbdavid / LiteDB

LiteDB - A .NET NoSQL Document Store in a single data file
http://www.litedb.org
MIT License
8.62k stars 1.25k forks source link

[QUESTION] LiteDB 5.0.9 how connection=shared works #1911

Open bnfgrg opened 3 years ago

bnfgrg commented 3 years ago

Hi, I need to implement a control to count the number of running occurences of a windows form app written in C# running locally on my PC (.Net Framework 4.5). I thought to use LiteDB , where each instance of MyApp implements a polling (using Timers) to access a db file for reading and writing its PID (process ID) and last time update.

quest1

Here's the code:

class Instance
        {
            public int Id { get; set; }
            public int PID { get; set; }
            public DateTime lastupdate { get; set; }
        }
...
Public void myPolling() {

String CONNECTION= "Filename=c:\temp\mydbfile;Password=abc;Connection=shared";

using (var db = new LiteDatabase(DBFILENAME))
{

    var col = db.GetCollection<Instance>("instances");

    //remove all the zombie instances
    DateTime TimeOut = DateTime.Now.AddMinutes(-1);
    col.DeleteMany(x => x.lastupdate <= TimeOut);

    col = db.GetCollection<Instance>("instances");

    //look for the current PID, if found , udpdates the lastupdate and return
    var result = col.FindOne(x => x.PID == Process.GetCurrentProcess().Id);
    if (result != null)
        {
            result.lastupdate = DateTime.Now;
            col.Update(result);
            return true ;
        }

    //count the remaining istances
    col = db.GetCollection<Instance>("instances");
    if (col!=null && col.Count() >= 2)
    {
        MessageBox.Show("Max instances exceeded:" + col.Count(), "", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }

    //create the new istance
    var inst = new Instance{
        PID = Process.GetCurrentProcess().Id,
        lastupdate = DateTime.Now
    };

    col.Insert(inst);
    col.EnsureIndex(x => x.PID);

       return true;
}

In the connection string I set connection=shared but it seems that any instance of MyApp.exe points to its own mydbfile dabatase instead of sharing the same mydbfile.

If I try to change the connection to connection=direct or removing the connection parameter from CONNECTION string, each MyApp instance can share the same mydbfile and I can see the instance inside the db (using LiteDB Studio)

quest2

but after launcing other MyApp.exe I get the following error

System.IO.IOException: The process cannot access the file because it is being used by another process (realexception) System.IO.IOException: Il processo non può accedere al file 'C:\Users\bnf2\AppData\Local\Temp\xrvtmp' perché è in uso da un altro processo. in System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) in System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITYATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) in System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options) in LiteDB.Engine.FileStreamFactory.GetStream(Boolean canWrite, Boolean sequencial) ...

Where I'm wrong ? Can somebody help me ? Thanks :)

AlBannaTechno commented 3 years ago

@bnfgrg please edit your question to support syntax highlight by using

``` -your-code-- ``` but you should use ```C# -your-code-- ```

first, LiteDB can handle multiple connections in shared mode, and you can not do that in direct mode, because of file locks, so this why you got that exception

second, this is a simple code to handle multiple connections in the same app

 public class Issue1911
    {
        class Instance
        {
            public int Id { get; set; }
            public int Pid { get; set; }
            public DateTime LastUpdate { get; set; }

            public Instance()
            {
                LastUpdate = DateTime.Now;
            }
        }

        [Fact]
        public void ShouldHaveTheSameDbVersionOfSharedConnection()
        {
            if (File.Exists("db.ldb"))
            {
                File.Delete("db.ldb");
            }

            var db = "Filename=./db.ldb;Connection=shared";
            var db1 = new LiteDatabase(db);
            var db2 = new LiteDatabase(db);

            var instances1 = db1.GetCollection<Instance>("Instances");
            var instances2 = db2.GetCollection<Instance>("Instances");

            instances1.Insert(new Instance
            {
                Pid = 12234
            });

            instances2.Insert(new Instance
            {
                Pid = 22234
            });

            var all1 = instances1.FindAll().ToList();
            var all2 = instances2.FindAll().ToList();

            all1.Should().HaveCount(2);
            all2.Should().HaveCount(2);
        }
    }

third, multiple connections from many different apps will cause an issue, because if the instances run so quickly, LiteDB may not have time to commit transactions, so you will think that you have the same old DB version but the problem is that CPU cycle is incredibly fast compared to Disk IO cycles,

so you just used the wrong option,

the problem you try to solve, should not use any IO operation, It is very dangerous,

so, you have a few options

use service

create a services project, and connect that service to LiteDB, only that service, then use TCP local connection, to track your instances and you will use InMemory LiteDB instance, then every 10 seconds or any period you want commit that changes to a file and only load this file when service starts for the first time, so now you will not wast IO cycles

it is very easy to implement the usage of LiteDB is for persistence only, if you just need to use fresh versions of instances every time, you run the service, there is no need to use LiteDB

please read https://github.com/mbdavid/LiteDB/issues/1873 , https://github.com/mbdavid/LiteDB/issues/1752 , https://github.com/mbdavid/LiteDB/issues/1614 , https://github.com/mbdavid/LiteDB/issues/635 , https://github.com/mbdavid/LiteDB/issues/1614 , and finally, save your DB to file https://github.com/mbdavid/LiteDB/issues/1646

use Mutex

this is an advanced topic, but you can use it, it is very fast, and it the best if you understand exactly what you try to achieve

there are other options, but it is very advanced, and this is not the place where we should discuss, that, this is the LiteDB repository.

bnfgrg commented 3 years ago

thanks @AlBannaTechno for your answer. However, if I use connection=shared where each MyApp.exe writes to mydbfile adding a single Instance document , even if I wait for one or two minutes, I cant' see the Instance added in the instances on mydbfile. Should I see the instances added by the MyApp.exe applications ? Why I can't see them ?