Strange Exception

When opening a database in Xamarin.iOS. Is it something I should be worried about?

Thanks,
Martin

Database action raised exception System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.TypeInitializationException: The type initializer for ‘Couchbase.Lite.Store.ForestDBCouchStore’ threw an exception. —> System.EntryPointNotFoundException: c4rev_getGeneration
at (wrapper managed-to-native) CBForest.Native:c4rev_getGeneration (CBForest.C4Slice)
at CBForest.Native.c4rev_getGeneration (System.String revID) <0x1012276e0 + 0x00047> in :0
at Couchbase.Lite.Store.ForestDBCouchStore…cctor () <0x10112f1c0 + 0x00033> in :0
— End of inner exception stack trace —
at (wrapper managed-to-native) System.Reflection.MonoCMethod:InternalInvoke (System.Reflection.MonoCMethod,object,object[],System.Exception&)
at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) <0x1003d8380 + 0x00033> in :0
— End of inner exception stack trace —
at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) <0x1003d8380 + 0x00078> in :0
at System.RuntimeType.CreateInstanceMono (Boolean nonPublic) <0x1002d9060 + 0x0012b> in :0
at System.RuntimeType.CreateInstanceSlow (Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, System.Threading.StackCrawlMark& stackMark) <0x1002d9000 + 0x00047> in :0
at System.RuntimeType.CreateInstanceDefaultCtor (Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, System.Threading.StackCrawlMark& stackMark) <0x1002d8d70 + 0x00057> in :0
at System.Activator.CreateInstance (System.Type type, Boolean nonPublic) <0x100231600 + 0x0009f> in :0
at System.Activator.CreateInstance (System.Type type) <0x1002315d0 + 0x00017> in :0
at Couchbase.Lite.Database.OpenWithOptions (Couchbase.Lite.DatabaseOptions options) <0x1010b3970 + 0x0022b> in :0
at Couchbase.Lite.Manager.OpenDatabase (System.String name, Couchbase.Lite.DatabaseOptions options) <0x1010b8490 + 0x0007f> in :0
at Couchbase.Lite.Manager.GetDatabase (System.String name) <0x1010b85a0 + 0x0003b> in :0

Hey @borrrden is this something you can help with?

A few questions I need answered for this:

  1. Is this with or without the Couchbase.Lite.Storage.ForestDB nuget package installed?
  2. Does this crash the application or simply get logged?

In the static constructor I attempt to use one of the native ForestDB methods and catch the expected exception if the native components are not there to log a warning message about native components not being available, but this is a different exception than expected.

  1. It is with the Couchbase.Lite.Storage.ForestDB installed.
  2. I think the answer is yes. My code catches the exceptions and logs it. I guess you want to know if it is an exception that is handled inside Couchbase.

After some further digging I have found out that it does not happen in Release builds but not in Debug builds.

I’ve been able to reproduce this. It’s a shame, it seems that iOS throws a different exception than other platforms. I’ll probably repackage the nuget package to not perform this check or catch the other exception as well since this is a pretty serious issue. The odd thing is, it only happens for me when the ForestDB nuget package is not installed so the workaround I would suggest for now is installing it. Are you sure it is installed on your project?

It shows up under packages in the Xamarin Studio project,

but is not visible among the references

Is that would you expect to see after adding the Couchbase.Lite.Storage.ForestDB nuget package?

The fact that the exception is not thrown in a Debug build suggests to me that problem is something that goes wrong when installing and linking the shared object files. I am not very familiar with the .Net build process, so I might easily have missed something.

It won’t show up under the references because there is actually no managed code for it to reference. You can try deleting it and re-adding it, but the important part is that the “.targets” file shows up in your .csproj file. Also if for some reason this is not a top level project, the package needs to be added on the top level project (that being the project that actually gets deployed to the device and not an intermediate dll file)

You should see a line that looks like this:

<Import Project="packages\Couchbase.Lite.Storage.ForestDB.1.2\build\Xamarin.iOS10\Couchbase.Lite.Storage.ForestDB.targets" Condition="Exists('packages\Couchbase.Lite.Storage.ForestDB.1.2\build\Xamarin.iOS10\Couchbase.Lite.Storage.ForestDB.targets')" />

I verified that the import line was present in the project. As a temporary workaround I have downgraded to version 1.1.

As the exception was thrown in a Debug build but not in a Release build I believe that the the ForestDB library was installed in the Debug build but not in the Release build. Is this a release assumption?

My concern is that once you capture the exception correctly I might not find out that the ForestDB library was not correctly installed in a release build. My reasoning is that if there is something wrong with the Release build configuration that prevents ForestDB from being installed I will now not find it. Is there a way to verify that the ForestDB library was installed correctly?

Thanks a mile for your prompt and helpful support.

Martin

There are some telltale signs to look out for to determine which version of the ForestDB DLL is getting copied. iOS is especially awkward because it needs to embed a static library into the managed DLL itself so it requires two versions. There is a “fake” version without the native components in the System SQLite package, and the real version in the ForestDB package. If you see the file “cbforest-sharp.dll” in your output directory you should check its size. If it is small (around 33k) then it is the fake version but if it is large (around 33MB) then it is the real version. Did you check whether or not the line I mentioned is included in the project that gets deployed to the device and not in an intermediate DLL?

Any update on this? I’m running into this same problem. I have these lines in my .csproj file:

<Import Project="..\packages\Couchbase.Lite.Storage.SystemSQLite.1.2\build\Xamarin.iOS10\Couchbase.Lite.Storage.SystemSQLite.targets" Condition="Exists('..\packages\Couchbase.Lite.Storage.SystemSQLite.1.2\build\Xamarin.iOS10\Couchbase.Lite.Storage.SystemSQLite.targets')" />
<Import Project="..\packages\Couchbase.Lite.Storage.ForestDB.1.2.0.1\build\Xamarin.iOS10\Couchbase.Lite.Storage.ForestDB.targets" Condition="Exists('..\packages\Couchbase.Lite.Storage.ForestDB.1.2.0.1\build\Xamarin.iOS10\Couchbase.Lite.Storage.ForestDB.targets')" />

I also checked and the 33MB cbforest-sharp.dll file is in my output directory.

I think I may have found another issue with the package, unfortunately. In your output directory do you see a cbforest-sharp.dll.config file? It looks like I didn’t include that file in the targets file. If it is not there, then I bet copying it out of the build\Xamarin.iOS10\ directory will fix the problem. If it does I’ll push an updated package with the corrected targets file. If not, then I’m not sure yet.

Note: I have overhauled this system and starting in the next prerelease (hopefully next week) I won’t use the config file anymore and things will be a lot simpler.

@borrrden, At the moment I get this exception in a release build but it does not happen in debug. I can see both the cbforest-sharp.dll and the cbforest-sharp.dll.config file in the output directory.

You will see the cbforest-sharp.dll no matter what, it just depends on if it is the real one or the stub one. The stub one is 33kb and the real one is around 38 MB. As I mentioned before I have redone this awkward system and refactored ALL of the managed code into plugins to try to make it less confusing. I have made sure to include that in 1.2.1 which will be coming in a week or two. For now make sure you have the ForestDB project referenced, or the nuget installed on your app, and if you aren’t using ForestDB just catch and ignore this expcetion.

This is the call throws the exception

 _db = Manager.SharedInstance.GetDatabase(_databaseName);

which means I don’t get a database object to work with. How do I get around this?

Catch the exception and try it again. If we’re still talking about the same exception, it happens in the static constructor so it will only happen once.

This bizarre looking code catches the exception twice without creating a database object

try {
                    _db = Manager.SharedInstance.GetDatabase(_databaseName);
                } catch (Exception ex) {
                    LogService.SharedLogService.Error(String.Format("Opening the database raised exception {0}", ex));
                    try {
                        _db = Manager.SharedInstance.GetDatabase(_databaseName);
                    } catch (Exception ex2) {
                        LogService.SharedLogService.Error(String.Format("Opening the database a second time raised exception {0}", ex2));
                    }
                }

It looks to me like ForestDB is only C++. Might I resolve this by downloading and linking the static library libCBLForestDBStorage.a?

Otherwise, is there any chance you could furnish me with a beta build of 2.1.0?

Yes, if catching the exception does not work then adding the ForestDB nuget package and confirming that you have the 38 mb version will take care of the problem.

EDIT I can’t provide a beta build for you but I am happy to answer questions about building it from source (which is exactly what I would do if I were going to provide one). You can get the 1.2.1 WIP on the release/1.2.1 branch.

This candidate for ugly hack of the month makes the crash go away. Publishing it here in case someone else needs a short-term work-around until 1.2.1 is out.

using System;

namespace CBForest
{
   /// <summary>
   /// This cludge is expected to become redundant with versions 1.2.1 of Couchbase Lite.
   /// </summary>
   public static class Native
   {
      public static int c4rev_getGeneration(string revID)
      {
          // On iOS the exception  System.EntryPointNotFoundException is thrown instead
          // of DllNotFoundException which is what Jim Borden's code expect.
          throw new DllNotFoundException();
      }
  }
}

I’m surprised that works, I wouldn’t have thought to try it. I’m both glad and terrified that it does. I assume that a compiler warning is generated about duplicate symbols and the “imported” one (from the library) being ignored in favor of this one.

The terrifying part, though, is that this means that any of our functions can be overwritten easily by doing this (if someone were feeling particularly evil). I’m going to see if there is a way to prevent this (some kind of code security setting).

Recall that the symbol does not exist in our release build for iOS, maybe you will see a different behaviour on other platforms or if the symbol is defined twice for an iOS release build.

I expected to have to put in an #ifdef to prevent this from being compiled with the debug build. Surprisingly the linker is not emitting any warning about multiple symbols.

I find the entire Monotouch experience terrifying…