Assembly.LoadFrom fails but only sporadically

1

I want to write a program that watches a .dll for changes. When a change happens, it should load the assembly and invoke the foo function inside.

I have some code that should implement this, but it behaves strangely. Sometimes it works. Sometimes the assembly it loads will be an old version. Sometimes it will throw a BadImageFormatException exception.

Here is my program code (it is F# but I think this is a general .NET Core question):

module HotReloadDemo

open System
open System.IO
open System.Reflection

[<EntryPoint>]
let main argv =
  let assemblyPath = argv.[0] // Path to the .dll to watch

  let mutable lastWriteTime = DateTime.MinValue

  while true do

    let writeTime =
      if File.Exists assemblyPath
      then
        File.GetLastWriteTimeUtc assemblyPath
      else
        lastWriteTime

    if writeTime > lastWriteTime
    then
      lastWriteTime <- writeTime

      try
        printfn "Last write time: %O " lastWriteTime

        printfn "Waiting for the build to finish (this is a hack)... "
        Threading.Thread.Sleep 10000 // 10s is plenty long enough for the build to finish

        printfn "Loading assembly path from: %s " assemblyPath

        let assembly = Assembly.LoadFrom assemblyPath

        printfn "Got assembly: %O" (assembly.GetName ())

        let foo : (Unit -> int) option =
          assembly.GetExportedTypes()
          |> Array.tryHead
          |> Option.bind (fun t -> t.GetMethod "foo" |> Option.ofObj)
          |> Option.map (fun m -> (fun () -> m.Invoke (null, Array.empty) :?> int))

        match foo with
        | Some foo ->
          printfn "foo () = %O" (foo ())
        | None ->
          printfn "foo not found"

      with exn ->
        printfn "%O" exn

    else
      ()

  Threading.Thread.Sleep 1000

  0

I then have a very simple library to be watched in another project like this:

module HotReload

  let foo () =
    123456

To test it, I launch the "watcher" program. It successfully loads and invokes foo.

Then, I modify my library (e.g. to return a different number) and build it with dotnet build.

The watcher detects the change, loads the assembly again and invokes foo, but it prints the number from before the change!

Then, I modify the library again with a different number. It detects the change but crashes:

...

Loading assembly path from: ../hot-reload-lib/bin/Debug/netstandard2.0/hot-reload-lib.dll 
System.BadImageFormatException: Could not load file or assembly '<Unknown>'. Index not found. (0x80131124)
File name: '<Unknown>'
   at System.Runtime.Loader.AssemblyLoadContext.LoadFromPath(IntPtr ptrNativeAssemblyLoadContext, String ilPath, String niPath, ObjectHandleOnStack retAssembly)
   at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
   at System.Reflection.Assembly.LoadFrom(String assemblyFile)

...

What is going on here?


dotnet  --version 
3.0.100

lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.3 LTS
Release:    18.04
Codename:   bionic
.net-core
f#
.net-assembly
system.reflection
asked on Stack Overflow Feb 6, 2020 by sdgfsdh

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0