访问数据库对象突然抛出 EXC_BAD_INSTRUCTION

Accessing Database objects suddenly throws EXC_BAD_INSTRUCTION

我有一个问题我自己解决不了。

我正在使用 Vapor4、Fluent 以及用于后端应用程序的 postgres 数据库。 (见下文 Package.swift

有几种不同的模型 TransactionPlanItemBudget,它们对于收集财务统计数据都很有趣。在其他字段中,它们都有一个有效的日期间隔。此功能由 Statisticable-协议强制执行(见下文)

如果我现在根据 Statisticable 提供的值创建一个 DateInterval 每个 TransactionPlanItem 工作正常,只访问 Budget 对象抛出以下错误:

NIO-ELT-0-#8 (10): EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

这里唯一的区别是Budget不直接符合StatisticableBudget-objects 包装在 BudgetInterval-object 中,符合 Statisticable 并传递到统计生成器。

现在的问题是:这里出了什么问题?怎样才能找出原因呢?

Package.swift

// swift-tools-version:5.2
import PackageDescription

let package = Package(
    name: "backend",
    platforms: [
       .macOS(.v10_15)
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "4.35.0"),
        .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
        .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
        .package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-rc.2"),
        .package(url: "https://github.com/iLem0n/SwiftyBeaver.git", .exact("1.8.4")),
        .package(url: "../SwiftSpec", .branch("master")),
        .package(url: "https://github.com/Maxim-Inv/SwiftDate.git", .branch("master")),
        .package(url: "https://github.com/vapor/queues.git", from: "1.0.0"),
        .package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0-rc.1"),
        .package(url: "https://github.com/dehesa/CodableCSV", from: "0.6.2")
    ],
    targets: [
        .target(
            name: "App",
            dependencies: [
                .product(name: "Fluent", package: "fluent"),
                .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
                .product(name: "Vapor", package: "vapor"),
                .product(name: "JWT", package: "jwt"),
                .product(name: "SwiftyBeaver", package: "SwiftyBeaver"),                
                .product(name: "SwiftDate", package: "SwiftDate"),
                .product(name: "SwiftSpec", package: "SwiftSpec"),
                .product(name: "CodableCSV", package: "CodableCSV"),
                .product(name: "Queues", package: "queues"),
                .product(name: "QueuesRedisDriver", package: "queues-redis-driver")
            ],
            swiftSettings: [           
                .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
            ]
        ),
        .target(name: "Run", dependencies: [.target(name: "App")]),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)

Statisticable.swift

public protocol Statisticable: CustomDebugStringConvertible {
    var amountPerInstance: Double { get }
    var repetition: RepetitionType { get }
    var validFrom: Date { get }
    var validUntil: Date? { get }
    var validInterval: DateInterval? { get }
}

StatisticsGenerator.swift

public class StatisticsGenerator {
    private let latestBalance: Balance
    
    init(latestBalance: Balance) {
        self.latestBalance = latestBalance
    }
    
    func gatherStatistics(_ elements: [Statisticable], initial: Balance, lastDate: Date) -> [Date: DayStatistic] {
        var result: [Date: DayStatistic] = [:]
        
        let interval = DateInterval(start: initial.date, end: lastDate)
        for element in elements {

            switch element.repetition {
            case .once:
                if interval.contains(element.validFrom) {
                    upsert(amount: element.amountPerInstance, at: element.validFrom, result: &result)
                }
            default:       

                /* HERE IS THE ERROR, but only if the object is originally a `BudgetInterval` */
                let elementInterval = DateInterval(start: element.validFrom, end: element.validUntil ?? lastDate)
                
                                
                /* ... */
            }
        }
        
        
        
        return result
    }
    
    /* ... */ 
}

BudgetInterval.swift

struct BudgetInterval {
    let budget: Budget
    let interval: DateInterval
}

extension BudgetInterval: Statisticable {
    // accessing `budget.validFrom` throws error
    var validFrom: Date {        
        return max(budget.validFrom, interval.start)
    }
    
    var validUntil: Date? {
        if let validUntil = budget.validUntil {
            return min(validUntil, interval.end)
        }
        return interval.end
    }
    
    var repetition: RepetitionType {
        .daily
    }
    
    var amountPerInstance: Double {
        budget.maxExpense / Double(interval.numberOf(.day)!)
    }
}

Budget.swift

final class Budget: Model {
    static let schema: String = "budgets"
    
    @ID(key: .id)                   var id: UUID?
    @Parent(key: .ownerId)          var owner: User
    
    @Field(key: .name)              var name: String
    @Field(key: .maxExpense)        var maxExpense: Double
    @Field(key: .validFrom)         var validFrom: Date
    @Field(key: .validUntil)        var validUntil: Date?    
    
    /* init stuff */
}

Transaction.swift(对比)

final class Transaction: Model {
    static var schema: String = "transactions"

    @ID(custom: .id)                    var id: String?
    @Parent(key: .ownerId)              var owner: User
    @Field(key: .receiver)              var receiver: String?
    @Field(key: .reason)                var reason: String
    @Field(key: .amount)                var amount: Double
    @Field(key: .date)                  var date: Date

    /* init stuff */
 
}

extension Transaction: Statisticable {    
    var amountPerInstance: Double {
        return self.amount
    }
    
    // accessing this works fine
    var validFrom: Date {
        return self.date.dateAtStartOf(.day).date
    }
    
    var validUntil: Date? {        
        return self.date.dateAtEndOf(.day).date
    }
}

发现问题不是数据库访问本身,而是我生成了一个开始日期晚于结束日期的场景。因此无法构造 DateInterval。 在这种情况下,我有点失望 Foundation-class 没有检查这种常见问题。

无论如何,如果我之前检查过这个边缘情况,它就可以工作。

谢谢