1
\$\begingroup\$

In my web application, I'm utilizing a PDF library which is using a native DLL.

I'm using it for two very different purposes in two completely separate (and isolated by NuGet) parts of the web application.

Upon application start, I load the library DLL and thus lock the file. Then, rebuild requires completely terminating the application which takes much time and disrupts our workflow.

We cannot load and unload the DLL on every operation, as the operation will be executed lots of times in bursts. This will also not be thread safe.

So in order to alleviate this, I have decided to make a small wrapper to the PDF library that:

  • for every operation (it will only have 2) it will check if the library is loaded.
  • If not, it loads it. After the operation, it sets a timer that will unload the DLL in, let's say 10 seconds.
  • If another operation is executed within this timeframe, the timer is reset.

This is the code I have written so far to achieve this, appreciate any pointers.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace PdfLibrary
{
    public static class PdfLibrary
    {
        static object Lock = new object();
        static TimeSpan Timeout = new TimeSpan(0, 0, 10);
        static bool IsLoaded = false;
        static Timer UnloadTimer = new Timer();

        static PdfLibrary()
        {
            UnloadTimer.Interval = Timeout.TotalMilliseconds;
            UnloadTimer.Elapsed += (_, __) => Unload();
        }

        private static void Unload()
        {
            UnloadTimer.Stop();

            lock (Lock)
            {
                // ... unloads pdf library ...
            }
        }

        static void DoWhenLoaded(Action action)
        {
            if (!IsLoaded)
            {
                lock (Lock)
                {
                    if (!IsLoaded)
                    {
                        // ... loads pdf library ...
                    }
                }
            }

            action();

            UnloadTimer.Stop();
            UnloadTimer.Start();
        }

        public static IEnumerable<string> ExtractText(Stream inputStream)
        {
            IEnumerable<string> result = null;

            DoWhenLoaded(() => {
                // ... sets result ...
            });

            return result;
        }
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Another, perhaps simpler, option is to locally copy the dll and load the copy.

Then regularly check the original dll for an updated last modified time and then unload the copied dll, make a fresh copy and load the new copy.

Do keep in mind that when debugging a dll has a pdb file associated with it and loading the dll also locks the pdb. You can work around that by making the compiler emit the pdb with a different name each time.


However as is your implementation isn't thread safe. If the timer is about to trigger then action could still be running. Instead you will want to use a ReaderWriterLock, take a read lock when doing action() and a write lock when actually loading and unloading.

Lock.AcquireReaderLock(1000);
try{
    if (!IsLoaded)
    {
        LockCookie cookie = Lock.UpgradeToWriterLock(1000);
        try {
            //someone else may have gotten ahead in the queue
            if (!IsLoaded)
            {
                // ... loads pdf library ...
            }
        } finally {
            Lock.DowngradeFromWriterLock(cookie);
        }
    } 
    action();

} finally {
    Lock.ReleaseReaderLock();
}
\$\endgroup\$
3
  • \$\begingroup\$ Thanks for this, it's much appreciated. The action() should/could be left to being after the last finally though, right? It should be executed regardless of earlier IsLoaded status. I also would like an instruction as to how the Unload method should be constructed? \$\endgroup\$ Commented Apr 5, 2017 at 17:56
  • \$\begingroup\$ But you rely on the library being loaded before the invocation and not unloaded until it returns. Unload just takes the write lock and unloads (setting IsLoaded to false) \$\endgroup\$ Commented Apr 5, 2017 at 18:40
  • \$\begingroup\$ Oh, so the action() needs to be inside the try. But not conditioned by an else, right? \$\endgroup\$ Commented Apr 6, 2017 at 12:19

You must log in to answer this question.