func newCUPSCore(maxConnections uint, connectTimeout time.Duration) (*cupsCore, error) { host := C.cupsServer() port := C.ippPort() encryption := C.cupsEncryption() timeout := C.int(connectTimeout / time.Millisecond) var e string switch encryption { case C.HTTP_ENCRYPTION_ALWAYS: e = "encrypting ALWAYS" case C.HTTP_ENCRYPTION_IF_REQUESTED: e = "encrypting IF REQUESTED" case C.HTTP_ENCRYPTION_NEVER: e = "encrypting NEVER" case C.HTTP_ENCRYPTION_REQUIRED: e = "encryption REQUIRED" default: encryption = C.HTTP_ENCRYPTION_REQUIRED e = "encrypting REQUIRED" } var hostIsLocal bool if h := C.GoString(host); strings.HasPrefix(h, "/") || h == "localhost" { hostIsLocal = true } cs := lib.NewSemaphore(maxConnections) cp := make(chan *C.http_t) cc := &cupsCore{host, port, encryption, timeout, cs, cp, hostIsLocal} log.Infof("Connecting to CUPS server at %s:%d %s", C.GoString(host), int(port), e) // This connection isn't used, just checks that a connection is possible // before returning from the constructor. http, err := cc.connect() if err != nil { return nil, err } cc.disconnect(http) log.Info("Connected to CUPS server successfully") return cc, nil }
glibc < 2.20 and OSX 10.10 have problems when C.getaddrinfo is called many times concurrently. When the connector shares more than about 230 printers, and GCP is called once per printer in concurrent goroutines, http.Client.Do starts to fail with a lookup error. This solution, a semaphore, limits the quantity of concurrent HTTP requests, which also limits the quantity of concurrent calls to net.LookupHost (which calls C.getaddrinfo()). I would rather wait for the Go compiler to solve this problem than make this a configurable option, hence this long-winded comment. https://github.com/golang/go/issues/3575 https://github.com/golang/go/issues/6336 */ var lock *lib.Semaphore = lib.NewSemaphore(100) // newClient creates an instance of http.Client, wrapped with OAuth credentials. func newClient(oauthClientID, oauthClientSecret, oauthAuthURL, oauthTokenURL, refreshToken string, scopes ...string) (*http.Client, error) { config := oauth2.Config{ ClientID: oauthClientID, ClientSecret: oauthClientSecret, Endpoint: oauth2.Endpoint{ AuthURL: oauthAuthURL, TokenURL: oauthTokenURL, }, RedirectURL: RedirectURL, Scopes: scopes, } token := oauth2.Token{RefreshToken: refreshToken}
func NewPrinterManager(native NativePrintSystem, gcp *gcp.GoogleCloudPrint, privet *privet.Privet, printerPollInterval time.Duration, nativeJobQueueSize uint, jobFullUsername bool, shareScope string, jobs <-chan *lib.Job, xmppNotifications <-chan xmpp.PrinterNotification) (*PrinterManager, error) { var printers *lib.ConcurrentPrinterMap var queuedJobsCount map[string]uint var err error if gcp != nil { // Get all GCP printers. var gcpPrinters []lib.Printer gcpPrinters, queuedJobsCount, err = gcp.ListPrinters() if err != nil { return nil, err } // Organize the GCP printers into a map. for i := range gcpPrinters { gcpPrinters[i].NativeJobSemaphore = lib.NewSemaphore(nativeJobQueueSize) } printers = lib.NewConcurrentPrinterMap(gcpPrinters) } else { printers = lib.NewConcurrentPrinterMap(nil) } // Construct. pm := PrinterManager{ native: native, gcp: gcp, privet: privet, printers: printers, jobStatsMutex: sync.Mutex{}, jobsDone: 0, jobsError: 0, jobsInFlightMutex: sync.Mutex{}, jobsInFlight: make(map[string]struct{}), nativeJobQueueSize: nativeJobQueueSize, jobFullUsername: jobFullUsername, shareScope: shareScope, quit: make(chan struct{}), } // Sync once before returning, to make sure things are working. // Ignore privet updates this first time because Privet always starts // with zero printers. if err = pm.syncPrinters(true); err != nil { return nil, err } // Initialize Privet printers. if privet != nil { for _, printer := range pm.printers.GetAll() { err := privet.AddPrinter(printer, pm.printers.GetByNativeName) if err != nil { log.WarningPrinterf(printer.Name, "Failed to register locally: %s", err) } else { log.InfoPrinterf(printer.Name, "Registered locally") } } } pm.syncPrintersPeriodically(printerPollInterval) pm.listenNotifications(jobs, xmppNotifications) if gcp != nil { for gcpPrinterID := range queuedJobsCount { p, _ := printers.GetByGCPID(gcpPrinterID) go gcp.HandleJobs(&p, func() { pm.incrementJobsProcessed(false) }) } } return &pm, nil }
func (pm *PrinterManager) applyDiff(diff *lib.PrinterDiff, ch chan<- lib.Printer, ignorePrivet bool) { switch diff.Operation { case lib.RegisterPrinter: if pm.gcp != nil { if err := pm.gcp.Register(&diff.Printer); err != nil { log.ErrorPrinterf(diff.Printer.Name, "Failed to register: %s", err) break } log.InfoPrinterf(diff.Printer.Name+" "+diff.Printer.GCPID, "Registered in the cloud") if pm.gcp.CanShare() { if err := pm.gcp.Share(diff.Printer.GCPID, pm.shareScope, gcp.User, true, false); err != nil { log.ErrorPrinterf(diff.Printer.Name, "Failed to share: %s", err) } else { log.InfoPrinterf(diff.Printer.Name, "Shared") } } } diff.Printer.NativeJobSemaphore = lib.NewSemaphore(pm.nativeJobQueueSize) if pm.privet != nil && !ignorePrivet { err := pm.privet.AddPrinter(diff.Printer, pm.printers.GetByNativeName) if err != nil { log.WarningPrinterf(diff.Printer.Name, "Failed to register locally: %s", err) } else { log.InfoPrinterf(diff.Printer.Name, "Registered locally") } } ch <- diff.Printer return case lib.UpdatePrinter: if pm.gcp != nil { if err := pm.gcp.Update(diff); err != nil { log.ErrorPrinterf(diff.Printer.Name+" "+diff.Printer.GCPID, "Failed to update: %s", err) } else { log.InfoPrinterf(diff.Printer.Name+" "+diff.Printer.GCPID, "Updated in the cloud") } } if pm.privet != nil && !ignorePrivet && diff.DefaultDisplayNameChanged { err := pm.privet.UpdatePrinter(diff) if err != nil { log.WarningPrinterf(diff.Printer.Name, "Failed to update locally: %s", err) } else { log.InfoPrinterf(diff.Printer.Name, "Updated locally") } } ch <- diff.Printer return case lib.DeletePrinter: pm.native.RemoveCachedPPD(diff.Printer.Name) if pm.gcp != nil { if err := pm.gcp.Delete(diff.Printer.GCPID); err != nil { log.ErrorPrinterf(diff.Printer.Name+" "+diff.Printer.GCPID, "Failed to delete from the cloud: %s", err) break } log.InfoPrinterf(diff.Printer.Name+" "+diff.Printer.GCPID, "Deleted from the cloud") } if pm.privet != nil && !ignorePrivet { err := pm.privet.DeletePrinter(diff.Printer.Name) if err != nil { log.WarningPrinterf(diff.Printer.Name, "Failed to delete: %s", err) } else { log.InfoPrinterf(diff.Printer.Name, "Deleted locally") } } case lib.NoChangeToPrinter: ch <- diff.Printer return } ch <- lib.Printer{} }