I've been playing with runtime plugins reloading using Bundles in Swift. And I've encountered internal crash in conformsToProtocol call while using decoders (JsonDecoder, PlistDecoder etc).
This only happens when call PlistDecoder.decode then reload plugin using Bundle.unload, cleanup bundle, load with Bundle.load and call decode in new plugin instance again.
As I understand this related to the Swift and Objective-C runtime and the way Bundles/Frameworks are loaded. This doesn't happen if I use NSDictionary to parse plist or json because it doesn't use any runtime magic.
Does Swift supports this kind of reloading and if not why is that even possible in this case? Looks more like a bug to me.
Also is there a way to reload plugins in runtime without restarting an application?
Here's code snippets from my test application
Plugin Interface:
@objc(PluginInterface) protocol PluginInterface {
init()
func printVersion()
}
Plugin:
struct InfoPlist : Codable {
let CFBundleShortVersionString: String
}
class Plugin : PluginInterface {
required init() {}
func printVersion() {
print("Current version: \(getVersionFromPlistFile() ?? "nil")")
}
private func getVersionFromPlistFile() -> String? {
let infoPlistPath = Bundle.main.bundlePath + "/Contents/Info.plist"
guard let data = try? Data(contentsOf: URL(fileURLWithPath: infoPlistPath)) else {
return nil
}
let plistData = try? PropertyListDecoder().decode(InfoPlist.self, from: data)
return plistData?.CFBundleShortVersionString
}
}
Plugin loader:
class PluginLoader {
private var pluginBundle: Bundle?
deinit {
unloadDynamicBundle()
}
func load(bundleUrl: URL) -> PluginInterface? {
return loadDynamicBundle(bundleUrl: bundleUrl)
}
func unload() {
unloadDynamicBundle()
}
private func loadDynamicBundle(bundleUrl: URL) -> PluginInterface? {
guard let pluginBundle = Bundle(url: bundleUrl) else {
return nil
}
self.pluginBundle = pluginBundle
do {
try pluginBundle.loadAndReturnError()
} catch {
print("Loading error: \(error.localizedDescription)")
return nil
}
let typeNamed: AnyClass? = pluginBundle.classNamed("Plugin.Plugin")
guard let plugin = initPlugin(from: typeNamed as? PluginInterface.Type) else {
return nil
}
return plugin
}
private func unloadDynamicBundle() {
guard let pluginBundle = pluginBundle else {
return
}
if !pluginBundle.unload() {
return
}
self.pluginBundle = nil
}
private func initPlugin(from type: PluginInterface.Type?) -> PluginInterface? {
if let cls = type {
let plugin = cls.init()
return plugin
}
return nil
}
}
Here's crash stack trace:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x000000008dc005f0
Exception Codes: 0x0000000000000001, 0x000000008dc005f0
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace SIGNAL, Code 11 Segmentation fault: 11
Thread 1 Crashed:: Dispatch queue: com.apple.root.default-qos
0 libswiftCore.dylib 0x7ff8239aa710 swift_conformsToProtocolMaybeInstantiateSuperclasses(swift::TargetMetadata<swift::InProcess> const*, swift::TargetProtocolDescriptor<swift::InProcess> const*, bool) + 2464
1 libswiftCore.dylib 0x7ff8239a9b4e swift_conformsToProtocol + 78
2 libswiftCore.dylib 0x7ff823968fed swift::_conformsToProtocol(swift::OpaqueValue const*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetProtocolDescriptorRef<swift::InProcess>, swift::TargetWitnessTable<swift::InProcess> const**) + 45
3 libswiftCore.dylib 0x7ff8239a91a5 swift::_checkGenericRequirements(__swift::__runtime::llvm::ArrayRef<swift::TargetGenericRequirementDescriptor<swift::InProcess> >, __swift::__runtime::llvm::SmallVectorImpl<void const*>&, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>) + 1813
4 libswiftCore.dylib 0x7ff8239a3e19 _gatherGenericParameters(swift::TargetContextDescriptor<swift::InProcess> const*, __swift::__runtime::llvm::ArrayRef<swift::TargetMetadata<swift::InProcess> const*>, swift::TargetMetadata<swift::InProcess> const*, __swift::__runtime::llvm::SmallVectorImpl<unsigned int>&, __swift::__runtime::llvm::SmallVectorImpl<void const*>&, swift::Demangle::__runtime::Demangler&) + 1033
5 libswiftCore.dylib 0x7ff8239a2b03 (anonymous namespace)::DecodedMetadataBuilder::createBoundGenericType(swift::TargetContextDescriptor<swift::InProcess> const*, __swift::__runtime::llvm::ArrayRef<swift::TargetMetadata<swift::InProcess> const*>, swift::TargetMetadata<swift::InProcess> const*) const + 131
6 libswiftCore.dylib 0x7ff8239a119b swift::Demangle::__runtime::TypeDecoder<(anonymous namespace)::DecodedMetadataBuilder>::decodeMangledType(swift::Demangle::__runtime::Node*, unsigned int, bool) + 21499
7 libswiftCore.dylib 0x7ff82399b29d swift_getTypeByMangledNodeImpl(swift::MetadataRequest, swift::Demangle::__runtime::Demangler&, swift::Demangle::__runtime::Node*, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>) + 493
8 libswiftCore.dylib 0x7ff82399b06d swift_getTypeByMangledNode + 477
9 libswiftCore.dylib 0x7ff82399b78a swift_getTypeByMangledNameImpl(swift::MetadataRequest, __swift::__runtime::llvm::StringRef, void const* const*, std::__1::function<swift::TargetMetadata<swift::InProcess> const* (unsigned int, unsigned int)>, std::__1::function<swift::TargetWitnessTable<swift::InProcess> const* (swift::TargetMetadata<swift::InProcess> const*, unsigned int)>) + 1002
10 libswiftCore.dylib 0x7ff823998cbd swift_getTypeByMangledName + 477
11 libswiftCore.dylib 0x7ff823998eeb swift_getTypeByMangledNameInContext + 171
12 Plugin 0x10bc74cf9 __swift_instantiateConcreteTypeFromMangledName + 89
13 Plugin 0x10bc75033 protocol witness for Decodable.init(from:) in conformance InfoPlist + 19
14 libswiftCore.dylib 0x7ff82393a5e7 dispatch thunk of Decodable.init(from:) + 7
15 libswiftFoundation.dylib 0x7ff827a61cd8 __PlistDecoder.unbox<A>(_:as:) + 328