Example #1
0
File: tenet.go Project: axw/lingo
func (t *tenet) OpenService() (TenetService, error) {
	ds, err := t.Driver.Service()
	if err != nil {
		log.Print(err.Error()) // TODO(waigani) this logs are quick hacks. Work out the error paths and log them all at the root.
		return nil, errors.Trace(err)
	}

	cfg := &api.Config{}
	for k, v := range t.options {
		cfg.Options = append(cfg.Options,
			&api.Option{
				Name:  k,
				Value: fmt.Sprintf("%v", v),
			})
	}

	s := &tenetService{
		Service:      ds,
		cfg:          cfg,
		editFilename: t.Driver.EditFilename,
		editIssue:    t.Driver.EditIssue,
		mutex:        &sync.Mutex{},
	}

	if err := s.start(); err != nil {
		log.Println("got err opening service")
		// TODO(waigani) add retry logic here. 1. Keep retrying until service
		// is up. 2. Keep retrying until service is connected.
		log.Printf("err: %#v", errors.ErrorStack(err))

		return nil, errors.Trace(err)
	}
	log.Print("opened service, no issue")
	return s, nil
}
Example #2
0
func OSErrf(format string, a ...interface{}) {
	format = fmt.Sprintf("error: %s\n", format)
	errStr := fmt.Sprintf(format, a...)
	log.Print(errStr)
	Stderr.Write([]byte(errStr))
	Exiter(1)
}
Example #3
0
// Init the service.
func (d *Docker) Service() (Service, error) {
	log.Print("Docker Service called")

	// Ensure we have an image.
	c, err := d.getDockerClient()
	if err != nil {
		return nil, errors.Trace(err)
	}
	if !docker.HaveImage(c, d.Name) {

		// TODO(waigani) I think we should ask for user confirmation.
		fmt.Printf("\nno local image found for %s. Pulling new image from %s", d.Name, d.Registry)
		if err := d.Pull(false); err != nil {
			return nil, err
		}
	}

	return docker.NewService(d.Name)
}
Example #4
0
File: review.go Project: axw/lingo
// returns a chan of tenet reviews and a cancel chan that blocks until the user cancels.
func reviewQueue(ctx *cli.Context, mappings <-chan cfgMap, changed *map[string][]int, errc chan error) (<-chan *tenetReview, chan struct{}) {
	reviews := make(map[string]*tenetReview)
	reviewChannel := make(chan *tenetReview)
	cleanupWG := &sync.WaitGroup{}

	// setup a cancel exit path.
	cancelc := make(chan os.Signal, 1)
	signal.Notify(cancelc, os.Interrupt)
	signal.Notify(cancelc, syscall.SIGTERM)
	cancelledc := make(chan struct{})
	cancelled := func() bool {
		select {
		case _, ok := <-cancelledc:
			if ok {
				close(cancelc)
			}
			return true
		default:
			return false
		}
	}

	// Kill all open tenets on cancel.
	go func() {
		var i int
		for {
			<-cancelc
			if i > 0 {
				// on the second exit, just do it.
				fmt.Print("failed.\nSome docker containers may still be running.")
				os.Exit(1)
			}
			i++
			go func() {
				// TODO(waigani) add progress bar here
				fmt.Print("\ncleaning up tenets ... ")

				// Anything waiting on the cancelled chan will now fire.
				close(cancelledc)

				// Wait for all tenets to be cleaned up.
				cleanupWG.Wait()

				// say bye.
				fmt.Println("done.")
				os.Exit(1)
			}()
		}
	}()

	// TODO(waigani) reenable buffering to:
	// 1. Allow found tenets to keep running.
	// 2. Stop building new tenets until there is room in the buffer.
	// TODO(waigani) make cfg vars
	// buffLimit := 3
	// if ctx.Bool("keep-all") {
	// 	buffLimit = 100
	// }
	// buff := util.NewBuffer(buffLimit, cancelledc)

	go func() {
		for m := range mappings {
			// Glob all the files in the associated directories for this config, and assign to each tenet by hash
			for _, tc := range m.cfg.AllTenets() {

				if cancelled() {
					// empty files and dirs to stop feeding tenet reviews in progress.
					m.files = []string{}
					m.dirs = []string{}
					return
				}

				// Open the tenet service if we haven't seen this config before.
				configHash := tc.hash()
				r, found := reviews[configHash]
				if !found {

					// Don't build a new tenet until there is room in the buffer.
					// Found tenets will keep running until they are not fed files for 5 seconds.
					// WaitRoom will not block if we get a cancel signal.
					// buff.WaitRoom()

					tn, err := newTenet(ctx, tc)
					if err != nil {
						errc <- err
						continue
					}
					// Note: service should not be called outside this if block.
					service, err := tn.OpenService()
					if err != nil {
						errc <- err

						continue
					}

					info, err := service.Info()
					if err != nil {
						errc <- err
						continue
					}

					r = &tenetReview{
						configHash: configHash,
						filesc:     make(chan *api.File),
						issuesc:    make(chan *api.Issue),
						info:       info,
						issuesWG:   &sync.WaitGroup{},
						filesTM:    &tomb.Tomb{},
					}
					reviews[configHash] = r

					// Setup the takedown of this review.
					r.issuesWG.Add(1)
					cleanupWG.Add(1)
					// buff.Add(1)

					go func(r *tenetReview) {
						// The following fires when:
						select {
						// 1. all files have been sent or timed out
						// 2. the tenet buffer is full
						case <-r.filesTM.Dying():
							// 3. lingo has been stopped
						case <-cancelledc:
						}

						// make room for another tenet to start and ensure
						// that any configHash's matching this one will have
						// to start a new tenet instance.
						delete(reviews, configHash)
						// buff.Add(-1)

						// signal to the tenet that no more files are coming.

						close(r.filesc)

						// wait for the tenet to signal to us that it's finished it's review.
						r.issuesWG.Wait()

						// we can now safely close the backing service.
						if err := service.Close(); err != nil {
							log.Println("ERROR closing sevice:", err)
						}

						log.Println("cleanup done")
						cleanupWG.Done()
					}(r)

					// Make sure we're ready to handle results before we start
					// the review.
					reviewChannel <- r

					// Start this tenet's review.
					service.Review(r.filesc, r.issuesc, r.filesTM)
				}

				regexPattern, globPattern := fileExtFilterForLang(r.info.Language)
				for _, d := range m.dirs {
					files, err := filepath.Glob(path.Join(d, globPattern))
					if err != nil {
						// Non-fatal
						log.Printf("Error reading files in %s: %v\n", d, err)
					}

				l:
					for i, f := range files {
						select {
						case <-cancelledc:
							log.Println("user cancelled, dropping files.")
						case <-r.filesTM.Dying():
							dropped := len(files) - i
							log.Print("WARNING a tenet review timed out waiting for files to be sent. %d files dropped", dropped)
							break l
						case r.filesc <- &api.File{Name: f}:
						}
					}
				}
			z:
				for i, f := range m.files {
					if matches, err := regexp.MatchString(regexPattern, f); !matches {
						if err != nil {
							log.Println("error in regex: ", regexPattern)
						}
						continue
					}

					fileinfo := &api.File{Name: f}
					if changed != nil {
						if diffLines, ok := (*changed)[f]; ok { // This can be false if --diff and fileargs are specified
							for _, l := range diffLines {
								fileinfo.Lines = append(fileinfo.Lines, int64(l))
							}
						}
					}
					// TODO: Refactor so as not to have copy/pasted code with above dir handler
					select {
					case <-cancelledc:
						log.Println("user cancelled, dropping files.")
					case <-r.filesTM.Dying():
						dropped := len(m.files) - i
						log.Print("WARNING a tenet review timed out waiting for files to be sent. %d files dropped", dropped)
						break z
					case r.filesc <- fileinfo:
					}
				}
			}
		}

		for _, r := range reviews {

			// this says all files have been sent. For this review.
			r.filesTM.Done()
		}

		// wait for all tenets to be cleaned up.
		cleanupWG.Wait()

		// Closing this chan will start the wind down to end the lingo
		// process.
		close(reviewChannel)
	}()

	return reviewChannel, cancelledc
}