微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 Process() 和多线程的 Swift 命令行工具在执行一定数量的轮次后崩溃~3148 Main.swiftPasswordGenerator.swift加密.swiftProcess+Pipe.swift

如何解决使用 Process() 和多线程的 Swift 命令行工具在执行一定数量的轮次后崩溃~3148 Main.swiftPasswordGenerator.swift加密.swiftProcess+Pipe.swift

我在 Swift 中实现了一个密码生成器脚本,它利用 Process() 来执行 Mac OS X 命令行任务。密码本身只是随机字符串,然后由命令行任务加密(bcrypt),如下所示:

/usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'

还使用多个线程并行生成密码及其哈希值。

注意:多线程命令行任务(与我尝试过的其他几个本机 Swift 库相比)都提高了性能执行时间大大缩短。

问题

程序在前 ~3148 轮运行良好,并且总是在这个数字附近崩溃(可能与运行的线程数相关)。 例如,如果我配置 2000 个密码,则代码按预期执行会终止而没有任何错误

常见错误信息

Process+Pipe.swift 函数catch 块中的 execute(...) 中设置断点会导致:

BREAKPOINT_1
Thread 1: signal SIGCHLD

取消注释四个 po error.localizedDescription "The operation Couldn\\U2019t be completed. (NSPOSIXErrorDomain error 9 - Bad file descriptor)" 代码片段以忽略错误时,以下错误最终使执行崩溃(再次在 //return self.hash(string,cost: cost) 中,但不一定在 execute(...) 块中):

catch
Program stops ...
Thread 32: EXC_BAD_ACCESS (code=2,address=0x700003e6bfd4)
... on manual continue ...
Thread 2: EXC_BAD_ACCESS (code=2,address=0x700007e85fd4)

代码

相关代码组件是 po process error: Trying to put the stack in unreadable memory at: 0x700003e6bf40. ,它初始化并启动(然后停止)Main.swift,然后循环 n 次以通过 PasswordGenerator 获取密码来自nextPassword()PasswordGenerator 本身利用 PasswordGenerator 扩展中的 execute(...) 来运行生成哈希的命令行任务。

Main.swift

Process

PasswordGenerator.swift

class Main { private static func generate(...) { ... PasswordGenerator.start() for _ in 0..<n { let nextPassword = PasswordGenerator.nextPassword() let readablePassword = nextPassword.readable let password = nextPassword.hash ... } PasswordGenerator.stop() ... } } 并行运行多个线程。 PasswordGenerator 尝试获取密码(如果 nextPassword() 数组中有密码)或等待 100 秒。

passwords

加密.swift

struct PasswordGenerator {
  typealias Password = (readable: String,hash: String)
  
  private static let semaphore = dispatchSemaphore(value: 1)
  private static var active = false
  private static var passwords: [Password] = []
  
  static func nextPassword() -> Password {
    self.semaphore.wait()
    if let password = self.passwords.popLast() {
      self.semaphore.signal()
      return password
    } else {
      self.semaphore.signal()
      sleep(100)
      return self.nextPassword()
    }
  }
  
  static func start(
      numberOfWorkers: UInt = 32,passwordLength: UInt = 10,cost: UInt = 10
  ) {
      self.active = true
      
      for id in 0..<numberOfWorkers {
          self.runWorker(id: id,passwordLength: passwordLength,cost: cost)
      }
  }
  
  static func stop() {
    self.semaphore.wait()
    self.active = false
    self.semaphore.signal()
  }
  
  private static func runWorker(
    id: UInt,cost: UInt = 10
  ) {
    dispatchQueue.global().async {
      var active = true
      repeat {
        // Update active.
        self.semaphore.wait()
        active = self.active
        print("numberOfPasswords: \(self.passwords.count)")
        self.semaphore.signal()
        
        // Generate Password.
        // Important: The bycrypt(cost: ...) step must be done outside the Semaphore!
        let readable = String.random(length: Int(passwordLength))
        let password = Password(readable: readable,hash: Encryption.hash(readable,cost: cost))
        
        // Add Password.
        self.semaphore.wait()
        self.passwords.append(password)
        self.semaphore.signal()
      } while active
    }
  }
}

Process+Pipe.swift

struct Encryption {
  static func hash(_ string: String,cost: UInt = 10) -> String {
    // /usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'
    
    let command = "/usr/sbin/htpasswd"
    let arguments: [String] = "-bnBC \(cost) '' \(string)".split(separator: " ").map(String.init)
    
    let result1 = Process.execute(
      command: command,//"/usr/sbin/htpasswd",arguments: arguments//["-bnBC","\(cost)","''",string]
    )
    
    let errorString1 = String(
      data: result1?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),encoding: String.Encoding.utf8
    ) ?? ""
    guard errorString1.isEmpty else {
//      return self.hash(string,cost: cost)
      fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed with error: \(errorString1)")
    }
    
    guard let output1 = result1?.output else {
//    return self.hash(string,cost: cost)
      fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed! No output.")
    }
      
    let command2 = "/usr/bin/tr"
    let arguments2: [String] = "-d ':\n'".split(separator: " ").map(String.init)
    
    let result2 = Process.execute(
      command: command2,arguments: arguments2,standardInput: output1
    )
    
    let errorString2 = String(
      data: result2?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),encoding: String.Encoding.utf8
    ) ?? ""
    guard errorString2.isEmpty else {
//      return self.hash(string,cost: cost)
      fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed with error: \(errorString2)")
    }
    
    guard let output2 = result2?.output else {
//      return self.hash(string,cost: cost)
      fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) Failed! No output.")
    }
    
    guard
      let hash = String(
        data: output2.fileHandleForReading.readDataToEndOfFile(),encoding: String.Encoding.utf8
      )?.replacingOccurrences(of: "$2y$",with: "$2a$")
    else {
      fatalError("Hash: String replacement Failed!")
    }
    
    return hash
  }
}

问题

  1. 为什么程序会崩溃?
  2. 为什么对于 2000 个密码这样的大数字也不会崩溃?
  3. 多线程实现是否正确?
  4. extension Process { static func execute( command: String,arguments: [String] = [],standardInput: Any? = nil ) -> (output: Pipe?,error: Pipe?)? { let process = Process() process.executableuRL = URL(fileURLWithPath: command) process.arguments = arguments let outputPipe = Pipe() let errorPipe = Pipe() process.standardOutput = outputPipe process.standardError = errorPipe if let standardInput = standardInput { process.standardInput = standardInput } do { try process.run() } catch { print(error.localizedDescription) // BREAKPOINT_1 return nil } process.waitUntilExit() return (output: outputPipe,error: errorPipe) } } 代码有问题吗?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。