github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/bind/keybase.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package keybase 5 6 import ( 7 "errors" 8 "fmt" 9 "net" 10 "os" 11 "path/filepath" 12 "runtime" 13 "runtime/debug" 14 "runtime/trace" 15 "sync" 16 "time" 17 18 "github.com/keybase/client/go/chat/globals" 19 "github.com/keybase/client/go/status" 20 "golang.org/x/sync/errgroup" 21 22 "strings" 23 24 "github.com/keybase/client/go/externals" 25 "github.com/keybase/client/go/kbfs/env" 26 "github.com/keybase/client/go/kbfs/fsrpc" 27 "github.com/keybase/client/go/kbfs/libgit" 28 "github.com/keybase/client/go/kbfs/libkbfs" 29 "github.com/keybase/client/go/kbfs/simplefs" 30 "github.com/keybase/client/go/libkb" 31 "github.com/keybase/client/go/logger" 32 "github.com/keybase/client/go/protocol/chat1" 33 "github.com/keybase/client/go/protocol/keybase1" 34 "github.com/keybase/client/go/service" 35 "github.com/keybase/client/go/uidmap" 36 "github.com/keybase/go-framed-msgpack-rpc/rpc" 37 context "golang.org/x/net/context" 38 ) 39 40 var kbCtx *libkb.GlobalContext 41 var kbChatCtx *globals.ChatContext 42 var kbSvc *service.Service 43 var conn net.Conn 44 var startOnce sync.Once 45 var logSendContext status.LogSendContext 46 47 var initMutex sync.Mutex 48 var initComplete bool 49 50 type PushNotifier interface { 51 LocalNotification(ident string, msg string, badgeCount int, soundName string, convID string, typ string) 52 DisplayChatNotification(notification *ChatNotification) 53 } 54 55 type NativeVideoHelper interface { 56 Thumbnail(filename string) []byte 57 Duration(filename string) int 58 } 59 60 // NativeInstallReferrerListener is implemented in Java on Android. 61 type NativeInstallReferrerListener interface { 62 // StartInstallReferrerListener is used to get referrer information from the 63 // google play store on Android (to implement deferred deep links). This is 64 // asynchronous (due to the underlying play store api being so): pass it a 65 // callback function which will be called with the referrer string once it 66 // is available (or an empty string in case of errors). 67 StartInstallReferrerListener(callback StringReceiver) 68 } 69 70 type StringReceiver interface { 71 CallbackWithString(s string) 72 } 73 74 // InstallReferrerListener is a wrapper around NativeInstallReferrerListener to 75 // work around gomobile/gobind limitations while preventing import cycles. 76 type InstallReferrerListener struct { 77 n NativeInstallReferrerListener 78 } 79 80 func (i InstallReferrerListener) StartInstallReferrerListener(callback service.StringReceiver) { 81 i.n.StartInstallReferrerListener(callback) 82 } 83 84 var _ service.InstallReferrerListener = InstallReferrerListener{} 85 86 func newInstallReferrerListener(n NativeInstallReferrerListener) service.InstallReferrerListener { 87 return InstallReferrerListener{n: n} 88 } 89 90 type videoHelper struct { 91 nvh NativeVideoHelper 92 } 93 94 func newVideoHelper(nvh NativeVideoHelper) videoHelper { 95 return videoHelper{ 96 nvh: nvh, 97 } 98 } 99 100 func (v videoHelper) ThumbnailAndDuration(ctx context.Context, filename string) ([]byte, int, error) { 101 return v.nvh.Thumbnail(filename), v.nvh.Duration(filename), nil 102 } 103 104 type ExternalDNSNSFetcher interface { 105 GetServers() []byte 106 } 107 108 type dnsNSFetcher struct { 109 externalFetcher ExternalDNSNSFetcher 110 } 111 112 func newDNSNSFetcher(d ExternalDNSNSFetcher) dnsNSFetcher { 113 return dnsNSFetcher{ 114 externalFetcher: d, 115 } 116 } 117 118 func (d dnsNSFetcher) processExternalResult(raw []byte) []string { 119 return strings.Split(string(raw), ",") 120 } 121 122 func (d dnsNSFetcher) GetServers() []string { 123 if d.externalFetcher != nil { 124 return d.processExternalResult(d.externalFetcher.GetServers()) 125 } 126 return getDNSServers() 127 } 128 129 var _ libkb.DNSNameServerFetcher = dnsNSFetcher{} 130 131 func flattenError(err error) error { 132 if err != nil { 133 return errors.New(err.Error()) 134 } 135 return err 136 } 137 138 func isInited() bool { 139 initMutex.Lock() 140 defer initMutex.Unlock() 141 return initComplete 142 } 143 144 func setInited() { 145 initMutex.Lock() 146 defer initMutex.Unlock() 147 initComplete = true 148 } 149 150 // InitOnce runs the Keybase services (only runs one time) 151 func InitOnce(homeDir, mobileSharedHome, logFile, runModeStr string, 152 accessGroupOverride bool, dnsNSFetcher ExternalDNSNSFetcher, nvh NativeVideoHelper, 153 mobileOsVersion string, isIPad bool, installReferrerListener NativeInstallReferrerListener, isIOS bool) { 154 startOnce.Do(func() { 155 if err := Init(homeDir, mobileSharedHome, logFile, runModeStr, accessGroupOverride, dnsNSFetcher, nvh, mobileOsVersion, isIPad, installReferrerListener, isIOS); err != nil { 156 kbCtx.Log.Errorf("Init error: %s", err) 157 } 158 }) 159 } 160 161 // Init runs the Keybase services 162 func Init(homeDir, mobileSharedHome, logFile, runModeStr string, 163 accessGroupOverride bool, externalDNSNSFetcher ExternalDNSNSFetcher, nvh NativeVideoHelper, 164 mobileOsVersion string, isIPad bool, installReferrerListener NativeInstallReferrerListener, isIOS bool) (err error) { 165 defer func() { 166 err = flattenError(err) 167 if err == nil { 168 setInited() 169 } 170 }() 171 172 fmt.Printf("Go: Initializing: home: %s mobileSharedHome: %s\n", homeDir, mobileSharedHome) 173 if isIOS { 174 // buffer of bytes 175 buffer = make([]byte, 300*1024) 176 } else { 177 const targetBufferSize = 300 * 1024 178 // bufferSize must be divisible by 3 to ensure that we don't split 179 // our b64 encode across a payload boundary if we go over our buffer 180 // size. 181 const bufferSize = targetBufferSize - (targetBufferSize % 3) 182 // buffer for the conn.Read 183 buffer = make([]byte, bufferSize) 184 } 185 186 var perfLogFile, ekLogFile, guiLogFile string 187 if logFile != "" { 188 fmt.Printf("Go: Using log: %s\n", logFile) 189 ekLogFile = logFile + ".ek" 190 fmt.Printf("Go: Using eklog: %s\n", ekLogFile) 191 perfLogFile = logFile + ".perf" 192 fmt.Printf("Go: Using perfLog: %s\n", perfLogFile) 193 guiLogFile = logFile + ".gui" 194 fmt.Printf("Go: Using guilog: %s\n", guiLogFile) 195 } 196 libkb.IsIPad = isIPad 197 198 // Reduce OS threads on mobile so we don't have too much contention with JS thread 199 oldProcs := runtime.GOMAXPROCS(0) 200 newProcs := oldProcs - 2 201 if newProcs <= 0 { 202 newProcs = 1 203 } 204 runtime.GOMAXPROCS(newProcs) 205 fmt.Printf("Go: setting GOMAXPROCS to: %d previous: %d\n", newProcs, oldProcs) 206 207 startTrace(logFile) 208 209 dnsNSFetcher := newDNSNSFetcher(externalDNSNSFetcher) 210 dnsServers := dnsNSFetcher.GetServers() 211 for _, srv := range dnsServers { 212 fmt.Printf("Go: DNS Server: %s\n", srv) 213 } 214 215 kbCtx = libkb.NewGlobalContext() 216 kbCtx.Init() 217 kbCtx.SetProofServices(externals.NewProofServices(kbCtx)) 218 219 var suffix string 220 if isIPad { 221 suffix = " (iPad)" 222 } 223 fmt.Printf("Go (GOOS:%s): Mobile OS version is: %q%v\n", runtime.GOOS, mobileOsVersion, suffix) 224 kbCtx.MobileOsVersion = mobileOsVersion 225 226 // 10k uid -> FullName cache entries allowed 227 kbCtx.SetUIDMapper(uidmap.NewUIDMap(10000)) 228 kbCtx.SetServiceSummaryMapper(uidmap.NewServiceSummaryMap(1000)) 229 usage := libkb.Usage{ 230 Config: true, 231 API: true, 232 KbKeyring: true, 233 } 234 runMode, err := libkb.StringToRunMode(runModeStr) 235 if err != nil { 236 return err 237 } 238 config := libkb.AppConfig{ 239 HomeDir: homeDir, 240 MobileSharedHomeDir: mobileSharedHome, 241 LogFile: logFile, 242 EKLogFile: ekLogFile, 243 PerfLogFile: perfLogFile, 244 GUILogFile: guiLogFile, 245 RunMode: runMode, 246 Debug: true, 247 LocalRPCDebug: "", 248 VDebugSetting: "mobile", // use empty string for same logging as desktop default 249 SecurityAccessGroupOverride: accessGroupOverride, 250 ChatInboxSourceLocalizeThreads: 2, 251 LinkCacheSize: 1000, 252 } 253 if err = kbCtx.Configure(config, usage); err != nil { 254 fmt.Printf("failed to configure: %s\n", err) 255 return err 256 } 257 258 kbSvc = service.NewService(kbCtx, false) 259 if err = kbSvc.StartLoopbackServer(libkb.LoginAttemptOffline); err != nil { 260 fmt.Printf("failed to start loopback: %s\n", err) 261 return err 262 } 263 kbCtx.SetService() 264 uir := service.NewUIRouter(kbCtx) 265 kbCtx.SetUIRouter(uir) 266 kbCtx.SetDNSNameServerFetcher(dnsNSFetcher) 267 if err = kbSvc.SetupCriticalSubServices(); err != nil { 268 fmt.Printf("failed subservices setup: %s\n", err) 269 return err 270 } 271 kbSvc.SetupChatModules(nil) 272 if installReferrerListener != nil { 273 kbSvc.SetInstallReferrerListener(newInstallReferrerListener(installReferrerListener)) 274 } 275 kbSvc.RunBackgroundOperations(uir) 276 kbChatCtx = kbSvc.ChatContextified.ChatG() 277 kbChatCtx.NativeVideoHelper = newVideoHelper(nvh) 278 279 logs := status.Logs{ 280 Service: config.GetLogFile(), 281 EK: config.GetEKLogFile(), 282 Perf: config.GetPerfLogFile(), 283 } 284 285 fmt.Printf("Go: Using config: %+v\n", kbCtx.Env.GetLogFileConfig(config.GetLogFile())) 286 287 logSendContext = status.LogSendContext{ 288 Contextified: libkb.NewContextified(kbCtx), 289 Logs: logs, 290 } 291 292 // open the connection 293 if err = Reset(); err != nil { 294 fmt.Printf("failed conn setup %s\n", err) 295 return err 296 } 297 298 go func() { 299 kbfsCtx := env.NewContextFromGlobalContext(kbCtx) 300 kbfsParams := libkbfs.DefaultInitParams(kbfsCtx) 301 // Setting this flag will enable KBFS debug logging to always 302 // be true in a mobile setting. Change these back to the 303 // commented-out values if we need to make a mobile release 304 // before KBFS-on-mobile is ready. 305 kbfsParams.Debug = true // false 306 kbfsParams.Mode = libkbfs.InitConstrainedString // libkbfs.InitMinimalString 307 if _, err = libkbfs.Init( 308 context.Background(), kbfsCtx, kbfsParams, serviceCn{}, nil, 309 kbCtx.Log); err != nil { 310 fmt.Printf("unable to init KBFS: %s", err) 311 } 312 }() 313 314 return nil 315 } 316 317 func LogToService(str string) { 318 kbCtx.Log.Info(str) 319 } 320 321 type serviceCn struct{} 322 323 func (s serviceCn) NewKeybaseService(config libkbfs.Config, params libkbfs.InitParams, ctx libkbfs.Context, log logger.Logger) (libkbfs.KeybaseService, error) { 324 // TODO: plumb the func somewhere it can be called on shutdown? 325 gitrpc, _ := libgit.NewRPCHandlerWithCtx( 326 ctx, config, nil) 327 sfsIface, _ := simplefs.NewSimpleFS(ctx, config) 328 additionalProtocols := []rpc.Protocol{ 329 keybase1.SimpleFSProtocol(sfsIface), 330 keybase1.KBFSGitProtocol(gitrpc), 331 keybase1.FsProtocol(fsrpc.NewFS(config, log)), 332 } 333 keybaseService := libkbfs.NewKeybaseDaemonRPC( 334 config, ctx, log, true, additionalProtocols) 335 return keybaseService, nil 336 } 337 338 func (s serviceCn) NewCrypto(config libkbfs.Config, params libkbfs.InitParams, ctx libkbfs.Context, log logger.Logger) (libkbfs.Crypto, error) { 339 return libkbfs.NewCryptoClientRPC(config, ctx), nil 340 } 341 342 func (s serviceCn) NewChat(config libkbfs.Config, params libkbfs.InitParams, ctx libkbfs.Context, log logger.Logger) (libkbfs.Chat, error) { 343 return libkbfs.NewChatRPC(config, ctx), nil 344 } 345 346 // LogSend sends a log to Keybase 347 func LogSend(statusJSON string, feedback string, sendLogs, sendMaxBytes bool, traceDir, cpuProfileDir string) (res string, err error) { 348 defer func() { err = flattenError(err) }() 349 env := kbCtx.Env 350 logSendContext.UID = env.GetUID() 351 logSendContext.InstallID = env.GetInstallID() 352 logSendContext.StatusJSON = statusJSON 353 logSendContext.Feedback = feedback 354 logSendContext.Logs.GUI = env.GetGUILogFile() 355 logSendContext.Logs.Trace = traceDir 356 logSendContext.Logs.CPUProfile = cpuProfileDir 357 var numBytes int 358 switch kbCtx.MobileNetState.State() { 359 case keybase1.MobileNetworkState_WIFI: 360 numBytes = status.LogSendDefaultBytesMobileWifi 361 default: 362 numBytes = status.LogSendDefaultBytesMobileNoWifi 363 } 364 if sendMaxBytes { 365 numBytes = status.LogSendMaxBytes 366 } 367 368 logSendID, err := logSendContext.LogSend(sendLogs, numBytes, true /* mergeExtendedStatus */, true /* addNetworkStats */) 369 logSendContext.Clear() 370 return string(logSendID), err 371 } 372 373 // WriteArr sends raw bytes encoded msgpack rpc payload, ios only 374 func WriteArr(b []byte) (err error) { 375 bytes := make([]byte, len(b)) 376 copy(bytes, b) 377 defer func() { err = flattenError(err) }() 378 if conn == nil { 379 return errors.New("connection not initialized") 380 } 381 n, err := conn.Write(bytes) 382 if err != nil { 383 return fmt.Errorf("Write error: %s", err) 384 } 385 if n != len(bytes) { 386 return errors.New("Did not write all the data") 387 } 388 return nil 389 } 390 391 const bufferSize = 1024 * 1024 392 393 // buffer for the conn.Read 394 var buffer = make([]byte, bufferSize) 395 396 // ReadArr is a blocking read for msgpack rpc data. 397 // It is called serially by the mobile run loops. 398 func ReadArr() (data []byte, err error) { 399 defer func() { err = flattenError(err) }() 400 if conn == nil { 401 return nil, errors.New("connection not initialized") 402 } 403 n, err := conn.Read(buffer) 404 if n > 0 && err == nil { 405 return buffer[0:n], nil 406 } 407 408 if err != nil { 409 // Attempt to fix the connection 410 if ierr := Reset(); ierr != nil { 411 fmt.Printf("failed to Reset: %v\n", ierr) 412 } 413 return nil, fmt.Errorf("Read error: %s", err) 414 } 415 416 return nil, nil 417 } 418 419 // Reset resets the socket connection 420 func Reset() error { 421 if conn != nil { 422 conn.Close() 423 } 424 if kbCtx == nil || kbCtx.LoopbackListener == nil { 425 return nil 426 } 427 428 var err error 429 conn, err = kbCtx.LoopbackListener.Dial() 430 if err != nil { 431 return fmt.Errorf("Socket error: %s", err) 432 } 433 return nil 434 } 435 436 // ForceGC Forces a gc 437 func ForceGC() { 438 fmt.Printf("Flushing global caches\n") 439 kbCtx.FlushCaches() 440 fmt.Printf("Done flushing global caches\n") 441 442 fmt.Printf("Starting force gc\n") 443 debug.FreeOSMemory() 444 fmt.Printf("Done force gc\n") 445 } 446 447 // Version returns semantic version string 448 func Version() string { 449 return libkb.VersionString() 450 } 451 452 func IsAppStateForeground() bool { 453 if !isInited() { 454 return false 455 } 456 return kbCtx.MobileAppState.State() == keybase1.MobileAppState_FOREGROUND 457 } 458 459 func SetAppStateForeground() { 460 if !isInited() { 461 return 462 } 463 defer kbCtx.Trace("SetAppStateForeground", nil)() 464 kbCtx.MobileAppState.Update(keybase1.MobileAppState_FOREGROUND) 465 } 466 func SetAppStateBackground() { 467 if !isInited() { 468 return 469 } 470 defer kbCtx.Trace("SetAppStateBackground", nil)() 471 kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND) 472 } 473 func SetAppStateInactive() { 474 if !isInited() { 475 return 476 } 477 defer kbCtx.Trace("SetAppStateInactive", nil)() 478 kbCtx.MobileAppState.Update(keybase1.MobileAppState_INACTIVE) 479 } 480 func SetAppStateBackgroundActive() { 481 if !isInited() { 482 return 483 } 484 defer kbCtx.Trace("SetAppStateBackgroundActive", nil)() 485 kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUNDACTIVE) 486 } 487 488 func waitForInit(maxDur time.Duration) error { 489 if isInited() { 490 return nil 491 } 492 maxCh := time.After(maxDur) 493 for { 494 select { 495 case <-time.After(200 * time.Millisecond): 496 if isInited() { 497 return nil 498 } 499 case <-maxCh: 500 return errors.New("waitForInit timeout") 501 } 502 } 503 } 504 505 func BackgroundSync() { 506 // On Android there is a race where this function can be called before Init when starting up in the 507 // background. Let's wait a little bit here for Init to get run, and bail out if it never does. 508 if err := waitForInit(5 * time.Second); err != nil { 509 return 510 } 511 defer kbCtx.Trace("BackgroundSync", nil)() 512 513 // Skip the sync if we aren't in the background 514 if state := kbCtx.MobileAppState.State(); state != keybase1.MobileAppState_BACKGROUND { 515 kbCtx.Log.Debug("BackgroundSync: skipping, app not in background state: %v", state) 516 return 517 } 518 519 nextState := keybase1.MobileAppState_BACKGROUNDACTIVE 520 kbCtx.MobileAppState.Update(nextState) 521 doneCh := make(chan struct{}) 522 go func() { 523 defer func() { close(doneCh) }() 524 select { 525 case state := <-kbCtx.MobileAppState.NextUpdate(&nextState): 526 // if literally anything happens, let's get out of here 527 kbCtx.Log.Debug("BackgroundSync: bailing out early, appstate change: %v", state) 528 return 529 case <-time.After(10 * time.Second): 530 kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND) 531 return 532 } 533 }() 534 <-doneCh 535 } 536 537 // pushPendingMessageFailure sends at most one notification that a message 538 // failed to send. We only notify the user about visible messages that have 539 // failed. 540 func pushPendingMessageFailure(obrs []chat1.OutboxRecord, pusher PushNotifier) { 541 for _, obr := range obrs { 542 if topicType := obr.Msg.ClientHeader.Conv.TopicType; obr.Msg.IsBadgableType() && topicType == chat1.TopicType_CHAT { 543 kbCtx.Log.Debug("pushPendingMessageFailure: pushing convID: %s", obr.ConvID) 544 pusher.LocalNotification("failedpending", 545 "Heads up! Your message hasn't sent yet, tap here to retry.", 546 -1, "default", obr.ConvID.String(), "chat.failedpending") 547 return 548 } 549 } 550 kbCtx.Log.Debug("pushPendingMessageFailure: skipped notification for: %d items", len(obrs)) 551 } 552 553 // AppWillExit is called reliably on iOS when the app is about to terminate 554 // not as reliably on android 555 func AppWillExit(pusher PushNotifier) { 556 if !isInited() { 557 return 558 } 559 defer kbCtx.Trace("AppWillExit", nil)() 560 ctx := context.Background() 561 obrs, err := kbChatCtx.MessageDeliverer.ActiveDeliveries(ctx) 562 if err == nil { 563 // We are about to get killed with messages still to send, let the user 564 // know they will get stuck 565 pushPendingMessageFailure(obrs, pusher) 566 } 567 kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND) 568 } 569 570 // AppDidEnterBackground notifies the service that the app is in the background 571 // [iOS] returning true will request about ~3mins from iOS to continue execution 572 func AppDidEnterBackground() bool { 573 if !isInited() { 574 return false 575 } 576 defer kbCtx.Trace("AppDidEnterBackground", nil)() 577 ctx := context.Background() 578 convs, err := kbChatCtx.MessageDeliverer.ActiveDeliveries(ctx) 579 if err != nil { 580 kbCtx.Log.Debug("AppDidEnterBackground: failed to get active deliveries: %s", err) 581 convs = nil 582 } 583 stayRunning := false 584 switch { 585 case len(convs) > 0: 586 kbCtx.Log.Debug("AppDidEnterBackground: active deliveries in progress") 587 stayRunning = true 588 case kbChatCtx.LiveLocationTracker.ActivelyTracking(ctx): 589 kbCtx.Log.Debug("AppDidEnterBackground: active live location in progress") 590 stayRunning = true 591 case kbChatCtx.CoinFlipManager.HasActiveGames(ctx): 592 kbCtx.Log.Debug("AppDidEnterBackground: active coin flip games in progress") 593 stayRunning = true 594 } 595 if stayRunning { 596 kbCtx.Log.Debug("AppDidEnterBackground: setting background active") 597 kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUNDACTIVE) 598 return true 599 } 600 SetAppStateBackground() 601 return false 602 } 603 604 func AppBeginBackgroundTaskNonblock(pusher PushNotifier) { 605 if !isInited() { 606 return 607 } 608 defer kbCtx.Trace("AppBeginBackgroundTaskNonblock", nil)() 609 go AppBeginBackgroundTask(pusher) 610 } 611 612 // AppBeginBackgroundTask notifies us that an app background task has been started on our behalf. This 613 // function will return once we no longer need any time in the background. 614 func AppBeginBackgroundTask(pusher PushNotifier) { 615 if !isInited() { 616 return 617 } 618 defer kbCtx.Trace("AppBeginBackgroundTask", nil)() 619 ctx := context.Background() 620 // Poll active deliveries in case we can shutdown early 621 beginTime := libkb.ForceWallClock(time.Now()) 622 ticker := time.NewTicker(5 * time.Second) 623 appState := kbCtx.MobileAppState.State() 624 if appState != keybase1.MobileAppState_BACKGROUNDACTIVE { 625 kbCtx.Log.Debug("AppBeginBackgroundTask: not in background mode, early out") 626 return 627 } 628 var g *errgroup.Group 629 g, ctx = errgroup.WithContext(ctx) 630 g.Go(func() error { 631 select { 632 case appState = <-kbCtx.MobileAppState.NextUpdate(&appState): 633 kbCtx.Log.Debug( 634 "AppBeginBackgroundTask: app state change, aborting with no task shutdown: %v", appState) 635 return errors.New("app state change") 636 case <-ctx.Done(): 637 return ctx.Err() 638 } 639 }) 640 g.Go(func() error { 641 ch, cancel := kbChatCtx.MessageDeliverer.NextFailure() 642 defer cancel() 643 select { 644 case obrs := <-ch: 645 kbCtx.Log.Debug( 646 "AppBeginBackgroundTask: failure received, alerting the user: %d marked", len(obrs)) 647 pushPendingMessageFailure(obrs, pusher) 648 return errors.New("failure received") 649 case <-ctx.Done(): 650 return ctx.Err() 651 } 652 }) 653 g.Go(func() error { 654 successCount := 0 655 for { 656 select { 657 case <-ticker.C: 658 obrs, err := kbChatCtx.MessageDeliverer.ActiveDeliveries(ctx) 659 if err != nil { 660 kbCtx.Log.Debug("AppBeginBackgroundTask: failed to query active deliveries: %s", err) 661 continue 662 } 663 if len(obrs) == 0 { 664 kbCtx.Log.Debug("AppBeginBackgroundTask: delivered everything: successCount: %d", 665 successCount) 666 // We can race the failure case here, so lets go a couple passes of no pending 667 // convs before we abort due to ths condition. 668 if successCount > 1 { 669 return errors.New("delivered everything") 670 } 671 successCount++ 672 } 673 curTime := libkb.ForceWallClock(time.Now()) 674 if curTime.Sub(beginTime) >= 10*time.Minute { 675 kbCtx.Log.Debug("AppBeginBackgroundTask: failed to deliver and time is up, aborting") 676 pushPendingMessageFailure(obrs, pusher) 677 return errors.New("time expired") 678 } 679 case <-ctx.Done(): 680 return ctx.Err() 681 } 682 } 683 }) 684 if err := g.Wait(); err != nil { 685 kbCtx.Log.Debug("AppBeginBackgroundTask: dropped out of wait because: %s", err) 686 } 687 } 688 689 func startTrace(logFile string) { 690 if os.Getenv("KEYBASE_TRACE_MOBILE") != "1" { 691 return 692 } 693 694 tname := filepath.Join(filepath.Dir(logFile), "svctrace.out") 695 f, err := os.Create(tname) 696 if err != nil { 697 fmt.Printf("error creating %s\n", tname) 698 return 699 } 700 fmt.Printf("Go: starting trace %s\n", tname) 701 _ = trace.Start(f) 702 go func() { 703 fmt.Printf("Go: sleeping 30s for trace\n") 704 time.Sleep(30 * time.Second) 705 fmt.Printf("Go: stopping trace %s\n", tname) 706 trace.Stop() 707 time.Sleep(5 * time.Second) 708 fmt.Printf("Go: trace stopped\n") 709 }() 710 }