0

Hi I have an app that uses a json file to monitor run intervals of emails. I use it as a simple way to store the times I run an email notification. But I've run into an issue where the user can run concurrent instances of this app. What happens then is that two instances can read that json file and if they both read that an email is not sent, both instances will send an email leading to duplicate emails.

This is how the code is (sampled down for replicability)

public class SummaryEmailStatus
{
    public DateTime DateSent { get; set; }
    public string ToolType { get; set; }
    public bool IsSent { get; set; }
}


public static class JsonUtil
{
    public static Dictionary<string, SummaryEmailStatus> EmailStatusKVP = new Dictionary<string, SummaryEmailStatus>();

    public static bool CreateEmailItemJasonFile(string jsonFileName)
    {
        var summCfgEmailStatus = new SummaryEmailStatus
        {
            DateSent = DateTime.Parse("2022-01-01"),
            ToolType = "CIM",
            IsSent = false
        };

        EmailStatusKVP.Add(SummaryEmailJsonObjs.ConfigError, summCfgEmailStatus);
        string json = JsonConvert.SerializeObject(EmailStatusKVP, Formatting.Indented);
        File.WriteAllText(jsonFileName, json);

    }

    public static Dictionary<string, SummaryEmailStatus>  ReadEmailItemJsonFromFile(string jsonFileName)
    {
        string json = File.ReadAllText(jsonFileName);
        EmailStatusKVP = JsonConvert.DeserializeObject<Dictionary<string, SummaryEmailStatus>>(json);

        return EmailStatusKVP;
    }

    public static void WriteSummEmailStatusJsonToFile(string summaryEmailType, SummaryEmailStatus emailItem)
    {
        //string json = JsonConvert.SerializeObject(emailItem, Formatting.Indented);
        EmailStatusKVP[summaryEmailType] = emailItem;
        string json = JsonConvert.SerializeObject(EmailStatusKVP, Formatting.Indented);
        File.WriteAllText(ParserConfigFilesConstants.SummaryEmailJsonFilePath, json);
    }

}

The issue is I am using File.WritallText and ReadAllText. Is there a way to do what I am doing but in a way that locks the file each time the CreateEmailItemJasonFile or ReadEmailItemJsonFromFile or WriteSummEmailStatusJsonToFile is called?

I want only one instance for the console application to use this file. If the other instance tries to use it, it should get some "being used by another program" exception.

I saw this solution How to lock a file with C#? but with how new I am to C# I am not sure how to use it for my own needs.

I also thought about using a lock object around my File.Write and File.Read sections but I was under the impression that would only work if its another thread within the console application instance:

lock (fileReadLock)
{
    string json = File.ReadAllText(jsonFileName);
}
Datboydozy
  • 131
  • 7
  • 1
    Locking the file while reading it will not work, you would need to lock the file for the whole read->send->write procedure. BTW. Preventing the app from running multiple times is an alternative solution. – tymtam Jun 22 '22 at 23:34
  • The app has to have multiple instances running different tools. So concurrent running is required – Datboydozy Jun 22 '22 at 23:35
  • Use a system-wide mutex for cross-process locking. https://stackoverflow.com/questions/3639138/cross-process-locking-in-c-sharp – nullforce Jun 22 '22 at 23:36
  • @nullforce but would that work for a file that is shared by different console apps? We run multiple batch files that call the exe which launches concurrent instances of that app – Datboydozy Jun 22 '22 at 23:57
  • @Datboydozy Yes, it's operating system wide. – nullforce Jun 23 '22 at 00:54
  • @nullforce so two different exes trying to access that file on that system will have to wait turns? Thats what you mean by system wide? Got it. And let's say its two different systems then looking to access that file, how would you lock it for reading and writing so only one process on one system gets it? – Datboydozy Jun 23 '22 at 01:07
  • Pick a `File.Open` which has a `FileShare` parameter to lock the file. https://learn.microsoft.com/en-us/dotnet/api/system.io.file.open?view=net-6.0 Then use a `StreamReader` / `StreamWriter` for the content while keeping the file locked. (note that you can specify `FileMode` to ensure the file is only created once and not overwritten) – Jeremy Lakeman Jun 23 '22 at 03:11
  • @JeremyLakeman Yes I looked further on and found a way to use filestream along with streamreader/writer and specified FileShare.None, Ive posted the answer to it. I wasn't ware of File.Open. will have to look into it sometime. – Datboydozy Jun 23 '22 at 05:32

1 Answers1

0

I fixed it by using FileStream: For reading I used:

FileStream fileStream = new FileStream(jsonFileName, FileMode.Open, FileAccess.Read, FileShare.None);
using (StreamReader reader = new StreamReader(fileStream))
{
    json = reader.ReadToEnd();
}

And for Writing I used:

FileStream fs = new FileStream(ParserConfigFilesConstants.SummaryEmailJsonFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
using (StreamWriter writer = new StreamWriter(fs))
{
    writer.WriteLine(json);
    writer.Flush();
}

This allows me to lock the file out whenever my app is using the file.

Datboydozy
  • 131
  • 7