You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							256 lines
						
					
					
						
							7.8 KiB
						
					
					
				
			
		
		
	
	
							256 lines
						
					
					
						
							7.8 KiB
						
					
					
				| package main
 | |
| 
 | |
| import (
 | |
| 	"container/list"
 | |
| 	"strconv"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // deferredTasks is a list of tasks that were deferred for processing to a later time.
 | |
| // This usually happens due to connection errors, protocol errors or per-endpoint limits.
 | |
| var deferredTasks *list.List
 | |
| 
 | |
| // taskMutex is a mutex for safe handling of deferred task list.
 | |
| var taskMutex sync.Mutex
 | |
| 
 | |
| func init() {
 | |
| 	deferredTasks = list.New()
 | |
| }
 | |
| 
 | |
| // TaskEvent represents all events that can be issued on a Task.
 | |
| type TaskEvent int
 | |
| 
 | |
| const (
 | |
| 	TE_Generic TaskEvent = iota // undefined or no event
 | |
| 
 | |
| 	// These should terminate a task instantly.
 | |
| 	TE_NoResponse    // connect timed out
 | |
| 	TE_ReadFailed    // read failed or timed out
 | |
| 	TE_NoService     // endpoint does not provide selected service
 | |
| 	TE_ProtocolError // a service module reported an error during auth attempt
 | |
| 	TE_Bad           // auth attempt completed successfully but credentials were wrong
 | |
| 	TE_Good          // auth attempt completed successfully and the credentials are correct
 | |
| 
 | |
| 	// TODO: proxying
 | |
| 	TE_ProxyNoResponse  // cannot connect to a proxy
 | |
| 	TE_ProxyError       // proxy failed during an exchange with the endpoint
 | |
| 	TE_ProxyInvalidAuth // authenticated proxy rejected our credentials
 | |
| 
 | |
| 	// These serve as "hints" - they do not necessarily need to
 | |
| 	// terminate a task, but they can still provide useful
 | |
| 	// information about an endpoint or a service.
 | |
| 	TH_NoSuchLogin  // login in this task is not present or not valid on a service
 | |
| 	TH_LoadExceeded // endpoint or service cannot handle this attempt rate
 | |
| 	TH_Banned       // got banned from a service, should try another proxy or wait out the delay
 | |
| 	TH_WaitRequest  // request for a grace time
 | |
| 
 | |
| 	// These are still hints, but they occur very frequently on a Task's normal lifecycle,
 | |
| 	// effectively making these sort of "notifications" rather than hints.
 | |
| 	TN_Connected      // successfully connected to an endpoint
 | |
| 	TN_ProxyConnected // successfully connected to a proxy
 | |
| )
 | |
| 
 | |
| func (ev TaskEvent) String() string {
 | |
| 	return [...]string{"Generic", "No response", "Read failed", "No Service", "Protocol error",
 | |
| 		"Bad", "Good", "No response from Proxy", "Error from Proxy", "Invalid auth from Proxy",
 | |
| 		"No Such login (hint)", "Load exceeded (hint)", "Banned (hint)", "Wait request (hint)",
 | |
| 		"Connected (notify)", "Connected from Proxy (notify)"}[ev]
 | |
| }
 | |
| 
 | |
| // A Task represents a single unit of workload.
 | |
| // Every Task is linked to an Endpoint.
 | |
| type Task struct {
 | |
| 	e            *Endpoint
 | |
| 	login        string
 | |
| 	password     string
 | |
| 	deferUntil   time.Time
 | |
| 	numDeferrals int
 | |
| 	listElement  *list.Element // position in list
 | |
| 	thread       int           // thread index
 | |
| }
 | |
| 
 | |
| // String returns a string representation of a Task.
 | |
| func (task *Task) String() string {
 | |
| 	if task == nil {
 | |
| 		return "<empty task>"
 | |
| 	} else {
 | |
| 		s := task.e.String() + "@" + task.login + ":" + task.password
 | |
| 		if task.thread > 0 {
 | |
| 			s = "[" + strconv.Itoa(task.thread) + "] " + s
 | |
| 		}
 | |
| 
 | |
| 		return s
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Defer sends a Task to the deferred queue.
 | |
| func (task *Task) Defer(addTime time.Duration) {
 | |
| 	task.deferUntil = time.Now().Add(addTime)
 | |
| 	task.numDeferrals++
 | |
| 
 | |
| 	maxDeferrals := getParamInt("task-max-deferrals")
 | |
| 	if maxDeferrals != -1 && task.numDeferrals >= maxDeferrals {
 | |
| 		log("task", 5, "giving up on task \"%v\" because it has exhausted its deferral limit (%v)", task, maxDeferrals)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	log("task", 5, "deferring task \"%v\" for %v", task, addTime)
 | |
| 
 | |
| 	taskMutex.Lock()
 | |
| 	defer taskMutex.Unlock()
 | |
| 
 | |
| 	if task.listElement == nil {
 | |
| 		task.listElement = deferredTasks.PushBack(task)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // EventWithParm tells a Task (and its underlying Endpoint) that
 | |
| // something important has happened, or a hint has been acquired.
 | |
| // Returns False if an event resulted in a deletion of its Task.
 | |
| func (task *Task) EventWithParm(event TaskEvent, parm any) bool {
 | |
| 	log("task", 4, "task event for \"%v\": %v", task, event)
 | |
| 
 | |
| 	if event == TE_Generic {
 | |
| 		return true // do not process generic events
 | |
| 	}
 | |
| 
 | |
| 	endpointOk := task.e.EventWithParm(event, parm) // notify the endpoint first
 | |
| 
 | |
| 	logIf(!endpointOk, "task", 4, "endpoint got deleted during a task event for \"%v\"", task)
 | |
| 
 | |
| 	switch event {
 | |
| 	// on these events, defer a Task only if its Endpoint is being kept
 | |
| 	case TE_NoResponse:
 | |
| 		if endpointOk {
 | |
| 			task.Defer(getParamDurationMS("no-response-delay-ms"))
 | |
| 		}
 | |
| 	case TE_ReadFailed:
 | |
| 		if endpointOk {
 | |
| 			task.Defer(getParamDurationMS("read-error-delay-ms"))
 | |
| 		}
 | |
| 	case TE_ProtocolError:
 | |
| 		if endpointOk {
 | |
| 			task.Defer(getParamDurationMS("protocol-error-delay-ms"))
 | |
| 		}
 | |
| 
 | |
| 	// report about a bad/good auth result
 | |
| 	case TE_Good:
 | |
| 		RegisterResult(task, true)
 | |
| 	case TE_Bad:
 | |
| 		RegisterResult(task, false)
 | |
| 
 | |
| 	// wait request has occurred: stop processing and instantly wait on a thread
 | |
| 	case TH_WaitRequest:
 | |
| 		log("task", 4, "wait request for \"%v\": sleeping for %v", task, parm.(time.Duration))
 | |
| 		time.Sleep(parm.(time.Duration))
 | |
| 	}
 | |
| 
 | |
| 	return endpointOk
 | |
| }
 | |
| 
 | |
| // Event is a parameterless version of EventWithParm.
 | |
| func (task *Task) Event(event TaskEvent) bool {
 | |
| 	return task.EventWithParm(event, 0)
 | |
| }
 | |
| 
 | |
| // GetDeferredTask retrieves a Task from the deferred queue.
 | |
| func GetDeferredTask() (task *Task, waitTime time.Duration) {
 | |
| 	currentTime := time.Now()
 | |
| 
 | |
| 	if deferredTasks.Len() == 0 {
 | |
| 		log("task", 5, "deferred task list is empty")
 | |
| 		return nil, 0
 | |
| 	}
 | |
| 
 | |
| 	minWaitTime := time.Time{}
 | |
| 
 | |
| 	for e := deferredTasks.Front(); e != nil; e = e.Next() {
 | |
| 		dt := e.Value.(*Task)
 | |
| 		if minWaitTime.IsZero() || (dt.deferUntil.Before(minWaitTime) && dt.deferUntil.After(currentTime)) {
 | |
| 			minWaitTime = dt.deferUntil
 | |
| 		}
 | |
| 
 | |
| 		if dt.deferUntil.Before(currentTime) && (dt.e.delayUntil.IsZero() || dt.e.delayUntil.Before(currentTime)) {
 | |
| 			deferredTasks.Remove(e)
 | |
| 			return dt, 0
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil, minWaitTime.Sub(currentTime)
 | |
| }
 | |
| 
 | |
| // FetchTaskComponents returns all components needed to build a Task.
 | |
| func FetchTaskComponents() (ep *Endpoint, login string, password string, waitTime time.Duration) {
 | |
| 	var empty bool
 | |
| 
 | |
| 	log("task", 5, "fetching components for a new task")
 | |
| 	ep, waitTime = FetchEndpoint()
 | |
| 	if ep == nil {
 | |
| 		return nil, "", "", waitTime
 | |
| 	}
 | |
| 
 | |
| 	log("task", 5, "fetched endpoint: \"%v\"", ep)
 | |
| 
 | |
| 	hasLogin := false
 | |
| 	for {
 | |
| 		log("task", 5, "fetching password for \"%v\"", ep)
 | |
| 		password, empty = SrcPassword.FetchOne(&ep.passwordPos, true)
 | |
| 		if !empty {
 | |
| 			log("task", 5, "got password for \"%v\": %v, fetching login", ep, password)
 | |
| 			if !hasLogin {
 | |
| 				login, empty = SrcLogin.FetchOne(&ep.loginPos, false)
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		log("task", 5, "out of passwords for \"%v\": resetting passwords and fetching new login", ep)
 | |
| 		ep.passwordPos.Reset()
 | |
| 		login, empty = SrcLogin.FetchOne(&ep.loginPos, true)
 | |
| 		hasLogin = true
 | |
| 		if empty {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !empty {
 | |
| 		log("task", 5, "got login for \"%v\": %v", ep, login)
 | |
| 		return ep, login, password, 0
 | |
| 	} else {
 | |
| 		log("task", 5, "out of logins for \"%v\": exhausting endpoint", ep)
 | |
| 		ep.Exhausted()
 | |
| 		return FetchTaskComponents() // attempt to fetch again with a new endpoint
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // CreateTask creates a new Task element. It searches through deferred queue first,
 | |
| // then, if nothing was found, it assembles a new (Endpoint, login, password) combination.
 | |
| func CreateTask(threadIdx int) (task *Task, delay time.Duration) {
 | |
| 	taskMutex.Lock()
 | |
| 	defer taskMutex.Unlock()
 | |
| 
 | |
| 	task, delayDeferred := GetDeferredTask()
 | |
| 	if task != nil {
 | |
| 		log("task", 4, "new task (deferred): %v", task)
 | |
| 		return task, 0
 | |
| 	}
 | |
| 
 | |
| 	ep, login, password, delayEndpoint := FetchTaskComponents()
 | |
| 	if ep == nil {
 | |
| 		if delayDeferred == 0 && delayEndpoint == 0 {
 | |
| 			log("task", 4, "cannot build task, no endpoint")
 | |
| 			return nil, 0
 | |
| 		} else if delayDeferred > delayEndpoint || delayDeferred == 0 {
 | |
| 			log("task", 4, "delaying task creation (by endpoint delay) for %v", delayEndpoint)
 | |
| 			return nil, delayEndpoint
 | |
| 		} else {
 | |
| 			log("task", 4, "delaying task creation (by deferred delay) for %v", delayDeferred)
 | |
| 			return nil, delayDeferred
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	t := Task{e: ep, login: login, password: password, thread: threadIdx}
 | |
| 	log("task", 4, "new task: %v", &t)
 | |
| 
 | |
| 	return &t, 0
 | |
| }
 | |
| 
 |