Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

package worker

import (
	
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
	
	
	
	
	
)
ProxyRemoved is a set of module@version that have been removed from the proxy, even though they are still in the index.
var ProxyRemoved = map[string]bool{}
fetchTask represents the result of a fetch task that was processed.
A Fetcher holds state for fetching modules.
FetchAndUpdateState fetches and processes a module version, and then updates the module_version_states table according to the result. It returns an HTTP status code representing the result of the fetch operation, and a non-nil error if this status code is not 200.
func ( *Fetcher) ( context.Context, , ,  string) ( int,  string,  error) {
	defer derrors.Wrap(&, "FetchAndUpdateState(%q, %q, %q)", , , )
	,  := trace.StartSpan(, "FetchAndUpdateState")
	 = experiment.NewContext(, experiment.FromContext().Active()...)
	 = log.NewContextWithLabel(, "fetch", +"@"+)
	if !utf8.ValidString() {
		log.Errorf(, "module path %q is not valid UTF-8", )
	}
	if  == internal.UnknownModulePath {
		return http.StatusInternalServerError, "", errors.New("called with internal.UnknownModulePath")
	}
	if !utf8.ValidString() {
		log.Errorf(, "requested version %q is not valid UTF-8", )
	}
	.AddAttributes(
		trace.StringAttribute("modulePath", ),
		trace.StringAttribute("version", ))
	defer .End()
Get the latest-version information first, and update the DB. We'll need it to determine if the current module version is the latest good one for its path.
The only errors possible here should be DB failures.
	if  != nil {
		return derrors.ToStatus(), "", 
	}
	 := .fetchAndInsertModule(, , , )
	.AddAttributes(trace.Int64Attribute("numPackages", int64(len(.PackageVersionStates))))
If there were any errors processing the module then we didn't insert it. Delete it in case we are reprocessing an existing module. However, don't delete if the error was internal, or we are shedding load.
	if .Status >= 400 && .Status < 500 {
		if  := deleteModule(, .DB, );  != nil {
			log.Error(, )
			.Error = 
			.Status = http.StatusInternalServerError
Do not return an error here, because we want to insert into module_version_states below.
Regardless of what the status code is, insert the result into version_map, so that a response can be returned for frontend_fetch.
	if  := updateVersionMap(, .DB, );  != nil {
		log.Error(, )
		if .Status != http.StatusInternalServerError {
			.Error = 
			.Status = http.StatusInternalServerError
Do not return an error here, because we want to insert into module_version_states below.
	}
If the requestedVersion was not successfully resolved to a semantic version, then at this point it will be the same as the resolvedVersion. This fetch request does not need to be recorded in module_version_states, since that table is only used to track modules that have been published to index.golang.org.
		return .Status, .ResolvedVersion, .Error
	}
Check if the latest good version of the module is not the one in search_documents, and insert it there and in imports_unique if so. Do not bother if this is an alternative module path.
	if .Status != derrors.ToStatus(derrors.AlternativeModule) ||  == nil || .CookedVersion != .ResolvedVersion {
		if  := .DB.ReInsertLatestVersion(, );  != nil {
			log.Error(, )
			if .Status != http.StatusInternalServerError {
				.Error = 
				.Status = http.StatusInternalServerError
Do not return an error here, because we want to insert into module_version_states below.
		}
	}
Update the module_version_states table with the new status of module@version. This must happen last, because if it succeeds with a code < 500 but a later action fails, we will never retry the later action. TODO(golang/go#39628): Split UpsertModuleVersionState into InsertModuleVersionState and UpdateModuleVersionState.
	 := time.Now()
	 := &postgres.ModuleVersionStateForUpsert{
		ModulePath:           .ModulePath,
		Version:              .ResolvedVersion,
		AppVersion:           ,
		Status:               .Status,
		HasGoMod:             .HasGoMod,
		GoModPath:            .GoModPath,
		FetchErr:             .Error,
		PackageVersionStates: .PackageVersionStates,
	}
	 = .DB.UpsertModuleVersionState(, )
	.timings["db.UpsertModuleVersionState"] = time.Since()
	if  != nil {
		log.Error(, )
		if .Error != nil {
			.Status = http.StatusInternalServerError
			.Error = fmt.Errorf("db.UpsertModuleVersionState: %v, original error: %v", , .Error)
		}
		logTaskResult(, , "Failed to update module version state")
		return http.StatusInternalServerError, .ResolvedVersion, .Error
	}
	logTaskResult(, , "Updated module version state")
	return .Status, .ResolvedVersion, .Error
}
fetchAndInsertModule fetches the given module version from the module proxy or (in the case of the standard library) from the Go repo and writes the resulting data to the database.
func ( *Fetcher) ( context.Context, ,  string,  *internal.LatestModuleVersions) *fetchTask {
	 := &fetchTask{
		FetchResult: fetch.FetchResult{
			ModulePath:       ,
			RequestedVersion: ,
		},
		timings: map[string]time.Duration{},
	}
	defer func() {
		derrors.Wrap(&.Error, "fetchAndInsertModule(%q, %q)", , )
		if .Error != nil {
			.Status = derrors.ToStatus(.Error)
			.ResolvedVersion = 
		}
	}()

	if ProxyRemoved[+"@"+] {
		log.Infof(, "not fetching %s@%s because it is on the ProxyRemoved list", , )
		.Error = derrors.Excluded
		return 
	}

	,  := .DB.IsExcluded(, )
	if  != nil {
		.Error = 
		return 
	}
	if  {
		.Error = derrors.Excluded
		return 
	}
Fetch the module, and the current @main and @master version of this module. The @main and @master version will be used to update the version_map target if applicable.
	var  sync.WaitGroup
	.Add(1)
	go func() {
		defer .Done()
		 := time.Now()
		 := fetch.FetchModule(, , , .ProxyClient, .SourceClient)
		if  == nil {
			panic("fetch.FetchModule should never return a nil FetchResult")
		}
		defer .Defer()
		.FetchResult = *
		.timings["fetch.FetchModule"] = time.Since()
Do not resolve the @main and @master version if proxy fetch is disabled.
	var  string
	.Add(1)
	go func() {
		defer .Done()
		if !.ProxyClient.FetchDisabled() {
			 = resolvedVersion(, , internal.MainVersion, .ProxyClient)
		}
	}()
	var  string
	.Add(1)
	go func() {
		defer .Done()
		if !.ProxyClient.FetchDisabled() {
			 = resolvedVersion(, , internal.MasterVersion, .ProxyClient)
		}
	}()
	.Wait()
	.MainVersion = 
	.MasterVersion = 
There was an error fetching this module.
	if .Error != nil {
		 := log.Infof
		if .Status >= 500 && .Status != derrors.ToStatus(derrors.ProxyTimedOut) {
			 = log.Warningf
		}
		(, "Error executing fetch: %v (code %d)", .Error, .Status)
		return 
	}
The module was successfully fetched.
	log.Infof(, "fetch.FetchModule succeeded for %s@%s", .ModulePath, .RequestedVersion)
Determine the current latest-version information for this module.

	 := time.Now()
	,  := .DB.InsertModule(, .Module, )
	.timings["db.InsertModule"] = time.Since()
	if  != nil {
		log.Error(, )

		.Status = derrors.ToStatus()
		.Error = 
		return 
	}
Invalidate the cache if we just processed the latest version of a module.
	if  {
Failure to invalidate the cache is not that serious; at worst it means some pages will be stale. (Cache TTLs for details pages configured in internal/frontend/server.go must not be too long, to account for this possibility.)
			log.Errorf(, "failed to invalidate cache for %s: %v", .ModulePath, )
		} else {
			log.Infof(, "invalidated cache for %s", .ModulePath)
		}
	}
	return 
}
invalidateCache deletes the series path for modulePath, as well as any possible URL path of which it is a componentwise prefix. That is, it deletes example.com/mod, example.com/mod@v1.2.3 and example.com/mod/pkg, but not the unrelated example.com/module. We delete the series path, not the module path, because adding a v2 module can affect v1 pages. For example, the first v2 module will add a "higher major version" banner to all v1 pages. While adding a v1 version won't currently affect v2 pages, that could change some day (for instance, if we decide to provide history). So it's better to be safe and delete all paths in the series.
func ( *Fetcher) ( context.Context,  string) error {
	if .Cache == nil {
		return nil
	}
	var  []error
All cache keys are request URLs, so they begin with "/".
	if  := .Cache.Delete(, "/"+);  != nil {
		 = append(, )
Delete all suffixes of the series path followed by a character that marks its end.
	for ,  := range "/@?#" {
		if  := .Cache.DeletePrefix(, fmt.Sprintf("/%s%c", , ));  != nil {
			 = append(, )
		}
	}
	if len() > 0 {
		return fmt.Errorf("%d errors, first is %w", len(), [0])
	}
	return nil
}

func ( context.Context, ,  string,  *proxy.Client) string {
	if  == stdlib.ModulePath &&  == internal.MainVersion {
		return ""
	}
	,  := fetch.GetInfo(, , , )
	if  != nil {
If an error occurs, log it as a warning and insert the module as normal.
			log.Warningf(, "fetch.GetInfo(ctx, %q, %q, f.ProxyClient, false): %v", , , )
		}
		return ""
	}
	return .Version
}

func ( context.Context,  *postgres.DB,  *fetchTask) ( error) {
	 := time.Now()
	defer func() {
		.timings["worker.updatedVersionMap"] = time.Since()
		derrors.Wrap(&, "updateVersionMap(%q, %q, %q, %d, %v)",
			.ModulePath, .RequestedVersion, .ResolvedVersion, .Status, .Error)
	}()
	,  := trace.StartSpan(, "worker.updateVersionMap")
	defer .End()

	var  string
	if .Error != nil {
		 = .Error.Error()
	}
If the resolved version for the this module version is also the resolved version for @main or @master, update version_map to match.
	 := []string{.RequestedVersion}
	if .MainVersion == .ResolvedVersion {
		 = append(, internal.MainVersion)
	}
	if .MasterVersion == .ResolvedVersion {
		 = append(, internal.MasterVersion)
	}
	for ,  := range  {
		 := 
		 := &internal.VersionMap{
			ModulePath:       .ModulePath,
			RequestedVersion: ,
			ResolvedVersion:  .ResolvedVersion,
			Status:           .Status,
			GoModPath:        .GoModPath,
			Error:            ,
		}
		if  := .UpsertVersionMap(, );  != nil {
			return 
		}
	}
	return nil
}

func ( context.Context,  *postgres.DB,  *fetchTask) ( error) {
	 := time.Now()
	defer func() {
		.timings["worker.deleteModule"] = time.Since()
		derrors.Wrap(&, "deleteModule(%q, %q, %q, %d, %v)",
			.ModulePath, .RequestedVersion, .ResolvedVersion, .Status, .Error)
	}()
	,  := trace.StartSpan(, "worker.deleteModule")
	defer .End()

	log.Infof(, "%s@%s: code=%d, deleting", .ModulePath, .ResolvedVersion, .Status)
	if  := .DeleteModule(, .ModulePath, .ResolvedVersion);  != nil {
		return 
If this was an alternative path (ft.Status == 491) and there is an older version in search_documents, delete it. This is the case where a module's canonical path was changed by the addition of a go.mod file. For example, versions of logrus before it acquired a go.mod file could have the path github.com/Sirupsen/logrus, but once the go.mod file specifies that the path is all lower-case, the old versions should not show up in search. We still leave their pages in the database so users of those old versions can still view documentation.
	if .Status == derrors.ToStatus(derrors.AlternativeModule) {
		log.Infof(, "%s@%s: code=491, deleting older version from search", .ModulePath, .ResolvedVersion)
		if  := .DeleteOlderVersionFromSearchDocuments(, .ModulePath, .ResolvedVersion);  != nil {
			return 
		}
	}
	return nil
}

func ( context.Context,  *fetchTask,  string) {
	var  []string
	for ,  := range .timings {
		 = append(, fmt.Sprintf("%s=%.3fs", , .Seconds()))
	}
	sort.Strings()
	 := strings.Join(, ", ")
	 := log.Infof
	if .Status == http.StatusInternalServerError {
		 = log.Errorf
	}
	(, "%s for %s@%s: code=%d, num_packages=%d, err=%v; timings: %s",
		, .ModulePath, .ResolvedVersion, .Status, len(.PackageVersionStates), .Error, )
}
FetchAndUpdateLatest fetches information about the latest versions from the proxy, and updates the database if the version has changed. It returns the most recent good information, which may be what it just fetched or may be what is already in the DB. It does not update the latest good version; that happens inside InsertModule, because it must be protected by the module-path advisory lock.
func ( *Fetcher) ( context.Context,  string) ( *internal.LatestModuleVersions,  error) {
	defer derrors.Wrap(&, "FetchAndUpdateLatest(%q)", )
	,  := fetch.LatestModuleVersions(, , .ProxyClient, func( string) (bool, error) {
		return .DB.HasGoMod(, , )
	})
	var  int
	switch {
	case  != nil:
		 = 200
There may be no version information for the module, even if it exists. In that case, we insert a 404 into the DB.
		 = 404
	default:
		 = derrors.ToStatus()
	}
	if  != 200 {
		return nil, .DB.UpdateLatestModuleVersionsStatus(, , )
	}
	return .DB.UpdateLatestModuleVersions(, )