Appointment.Save is not thread safe

nuget

<PackageReference Include="Aspose.Email" Version="23.8.0" />

repro

you may need to run this multiple times to get it to fail. for me it fails ~50% of the time

    [Test]
    public void Foo()
    {
        var aest = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
        var description =
            """
            <html>
              <head>
                <style>
                  * {
                    font-family: "Segoe UI", "Tahoma", "Verdana", sans-serif;
                    font-size: 11pt;
                  }
                </style>
              </head>
              <body>AAA</body>
            </html>
            """;
        var endDate = new DateTime(2020,10,2);
        var startDate = new DateTime(2020,10,1);

        var threads = new List<Thread>();
        var numOfThreads = 50;
        var waitHandles = new WaitHandle[numOfThreads];

        for (var i = 0; i < numOfThreads; i++)
        {
            var handle = new EventWaitHandle(false, EventResetMode.ManualReset);
            var thread = new Thread(() =>
            {
                var ics = new Appointment("location", startDate, endDate, "a@b.com", null)
                {
                    UniqueId = "aaa",
                    Summary = "title",
                    Description = description,
                    HtmlDescription = description,
                };
                ics.SetTimeZone(aest.StandardName);
                using var icsStream = new MemoryStream();
                ics.Save(icsStream);
                handle.Set();
            });
            threads.Add(thread);
            waitHandles[i] = handle;
        }

        foreach (var thread in threads)
        {
            thread.Start();
        }

        WaitHandle.WaitAll(waitHandles);
    }

Exceptions

When it fails it does so with a few different exceptions

Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
   at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
   at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)
   at pT.b(Int32 a)
   at zT.b(pT a, Int32 b, TimeSpan c, TimeSpan d)
   at zT.a(pT a, Int32 b, TimeSpan c, TimeSpan d, DateTimeKind e)
   at BT.a(Int32 a, List`1 b, vT c, DT d, TimeSpan e)
   at BT.b(Int32 a)
   at BT.b(DateTime a)
   at Jh.a(BT a, DateTime b)
   at Jh.a(Appointment a)
   at Jh.c()
   at Jh.JhO1dxa()
   at Aspose.Email.Calendar.CalendarWriter.Write(Appointment appointment)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream, AppointmentSaveOptions saveOptions)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream, AppointmentSaveFormat saveFormat)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream)
System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at System.Collections.Generic.Dictionary`2.set_Item(TKey key, TValue value)
   at pT.b(Int32 a)
   at zT.b(pT a, Int32 b, TimeSpan c, TimeSpan d)
   at zT.a(pT a, Int32 b, TimeSpan c, TimeSpan d, DateTimeKind e)
   at BT.a(Int32 a, List`1 b, vT c, DT d, TimeSpan e)
   at BT.b(Int32 a)
   at BT.b(DateTime a)
   at Jh.a(BT a, DateTime b)
   at Jh.a(Appointment a)
   at Jh.c()
   at Jh.JhO1dxa()
   at Aspose.Email.Calendar.CalendarWriter.Write(Appointment appointment)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream, AppointmentSaveOptions saveOptions)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream, AppointmentSaveFormat saveFormat)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream)
System.Collections.Generic.KeyNotFoundException: The given key '2019' was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at pT.b(Int32 a)
   at zT.b(pT a, Int32 b, TimeSpan c, TimeSpan d)
   at zT.a(pT a, Int32 b, TimeSpan c, TimeSpan d, DateTimeKind e)
   at BT.a(Int32 a, List`1 b, vT c, DT d, TimeSpan e)
   at BT.b(Int32 a)
   at BT.b(DateTime a)
   at Jh.a(BT a, DateTime b)
   at Jh.a(Appointment a)
   at Jh.c()
   at Jh.JhO1dxa()
   at Aspose.Email.Calendar.CalendarWriter.Write(Appointment appointment)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream, AppointmentSaveOptions saveOptions)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream, AppointmentSaveFormat saveFormat)
   at Aspose.Email.Calendar.Appointment.Save(Stream stream)

the fix

whatever shared Dictionary instance pT.b is using. make it a ConcurrentDictionary

Hello @simoncropp,

Thank you for reporting your case.

We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): EMAILNET-41153

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.

did the fix i proposed work?

Hello @simoncropp,

We have created an investigation ticket for your issue. It will be investigated in its turn.

Thanks for your patience.