pm

package module
Version: v0.0.0-...-baa672f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 15, 2020 License: MIT Imports: 7 Imported by: 2

README

pm

build

pm is a process manager with an HTTP interface. We use it at VividCortex to inspect and manage API server programs. It replaces an internal-only project that was similar.

pm is in beta and will change rapidly. Please see the issues list for what's planned, or to suggest changes.

A Processlist is useful for inspecting and managing what's running in a program, such as an HTTP server or other server program. Processes within this program are user-defined tasks, such as HTTP requests.

Documentation

Please read the generated package documentation for both server and client.

Getting Started

Package pm is a process manager with an HTTP monitoring/control interface.

Processes or tasks are user-defined routines within a running Go program. Think of the routines handling client requests in a web server, for instance. This package is designed to keep track of them, making information available through an HTTP interface. Client tools connecting to the later can thus monitor active tasks, having access to the full status history with timing data. Also, application-specific attributes may be attached to tasks (method/URI for the web server case, for example), that will be integrated with status/timing information.

Using pm starts by opening a server port to handle requests for task information through HTTP. That goes like this (although you probably want to add error checking/handling code):

go pm.ListenAndServe(":8081")

Processes to be tracked must call Start() with a process identifier and, optionally, a set of attributes. Even though the id is arbitrary, it's up to the application to choose one not in use by any other running task. A deferred call to Done() with the same id should follow:

pm.Start(requestID, nil, map[string]interface{}{
	"host":   req.RemoteAddr,
	"method": req.Method,
	"uri":    req.RequestURI,
})
defer pm.Done(requestID)

Finally, each task can change its status as often as required with a Status() call. Status strings are completely arbitrary and never inspected by the package. Now you're all set to try something like this:

curl http://localhost:8081/procs/
curl http://localhost:8081/procs/<id>/history

where <id> stands for an actual process id in your application. You'll get JSON responses including, respectively, the set of processes currently running and the full history for your chosen id.

Tasks can also be cancelled from the HTTP interface. In order to do that, you should call the DELETE method on /procs/<id>. Given the lack of support in Go to cancel a running routine, cancellation requests are implemented in this package as panics. Please refer to the full package documentation to learn how to properly deal with this. If you're not interested in this feature, you can disable cancellation completely by running the following before you Start() any task:

pm.SetOptions(ProclistOptions{
	ForbidCancel: true
})

See package pm/client for an HTTP client implementation you can readily use from Go applications.

Contributing

We only accept pull requests for minor fixes or improvements. This includes:

  • Small bug fixes
  • Typos
  • Documentation or comments

Please open issues to discuss new features. Pull requests for new features will be rejected, so we recommend forking the repository and making changes in your fork for your use case.

License

Copyright (c) 2013 VividCortex, licensed under the MIT license. Please see the LICENSE file for details.

Cat Picture

mechanic cat

Documentation

Overview

Package pm is a process manager with an HTTP monitoring/control interface.

To this package, processes or tasks are user-defined routines within a running program. This model is particularly suitable to server programs, with independent client-generated requests for action. The library would keep track of all such tasks and make the information available through HTTP, thus providing valuable insight into the running application. The client can also ask for a particular task to be canceled.

Using pm starts by calling ListenAndServe() in a separate routine, like so:

go pm.ListenAndServe(":8081")

Note that pm's ListenAndServe() will not return by itself. Nevertheless, it will if the underlying net/http's ListenAndServe() does. So it's probably a good idea to wrap that call with some error checking and retrying for production code.

Tasks to be tracked must be declared with Start(); that's when the identifier gets linked to them. An optional set of (arbitrary) attributes may be provided, that will get attached to the running task and be reported to the HTTP client tool as well. (Think of host, method and URI for a web server, for instance.) This could go like this:

pm.Start(requestID, nil, &map[string]interface{}{
	"host":   req.RemoteAddr,
	"method": req.Method,
	"uri":    req.RequestURI,
})
defer pm.Done(requestID)

Note the deferred pm.Done() call. That's essential to mark the task as finished and release resources, and has to be called no matter if the task succeeded or not. That's why it's a good idea to use Go's defer mechanism. In fact, the protection from cancel-related panics (see StopCancelPanic below) does NOT work if Done() is called in-line (i.e., not deferred) due to properties of recover().

An HTTP client issuing a GET call to /procs/ would receive a JSON response with a snapshot of all currently running tasks. Each one will include the time when it was started (as per the server clock) as well as the complete set of attributes provided to Start(). Note that to avoid issues with clock skews among servers, the current time for the server is returned as well.

The reply to the /procs/ GET also includes a status. When tasks start they are set to "init", but they may change their status using pm's Status() function. Each call will record the change and the HTTP client will receive the last status available, together with the time it was set. Furthermore, the client may GET /procs/<id>/history instead (where <id> is the task identifier) to have a complete history for the given task, including all status changes with their time information. However, note that task information is completely recycled as soon as a task is Done(). Hence, client applications should be prepared to receive a not found reply even if they've just seen the task in a /procs/ GET result.

Given the lack of a statement in Go to kill a routine, cancellations are implemented as panics. A DELETE call to /procs/<id> will mark the task with the given identifier as cancel-pending. Nevertheless, the task will never notice until it reaches another Status() call, which is by definition a cancellation point. Calls to Status() either return having set the new status, or panic with an error of type CancelErr. Needless to say, the application should handle this gracefully or will otherwise crash. Programs serving multiple requests will probably be already protected, with a recover() call at the level where the routine was started. But if that's not the case, or if you simply want the panic to be handled transparently, you may use this:

pm.SetOptions(ProclistOptions{
	StopCancelPanic: true
})

When the StopCancelPanic option is set (which is NOT the default) Done() will recover a panic due to a cancel operation. In such a case, the routine running that code will jump to the next statement after the invocation to the function that called Start(). (Read it again.) In other words, stack unfolding stops at the level where Done() was deferred. Notice, though, that this behavior is specifically tailored to panics raising from pending cancellation requests. Should any other panic arise for any reason, it will continue past Done() as usual. So will panics due to cancel requests if StopCancelPanic is not set.

Options set with pm.SetOptions() work on a global scope for the process list. Alternatively, you may provide a specific set for some task by using the options argument in the Start() call. If none is given, pm will grab the current global values at the time Start() was invoked. Task-specific options may come handy when, for instance, some tasks should be resilient to cancellation. Setting the ForbidCancel option makes the task effectively immune to cancel requests. Attempts by clients to cancel one of such tasks will inevitably yield an error message with no other effects. Set that with the global SetOptions() function and none of your tasks will cancel, ever.

HTTP clients can learn about pending cancellation requests. Furthermore, if a client request happens to be handled between the task is done/canceled and resource recycling (a VERY tiny time window), then the result would include one of these as the status: "killed", "aborted" or "ended", if it was respectively canceled, died out of another panic (not pm-related) or finished successfully. The "killed" status may optionally add a user-defined message, provided through the HTTP /procs/<id> DELETE method.

For the cancellation feature to be useful, applications should collaborate. Go lacks a mechanism to cancel arbitrary routines (it even lacks identifiers for them), so programs willing to provide the feature must be willing to help. It's a good practice to add cancellation points every once in a while, particularly when lengthy operations are run. However, applications are not expected to change status that frequently. This package provides the function CheckCancel() for that. It works as a cancellation point by definition, without messing with the task status, nor leaving a trace in history.

Finally, please note that cancellation requests yield panics in the same routine that called Start() with that given identifier. However, it's not unusual for servers to spawn additional Go routines to handle the same request. The application is responsible of cleaning up, if there are additional resources that should be recycled. The proper way to do this is by catching CancelErr type panics, cleaning-up and then re-panic, i.e.:

func handleRequest(requestId string) {
	pm.Start(requestId, map[string]interface{}{})
	defer pm.Done(requestId)
	defer func() {
		if e := recover(); e != nil {
			if _, canceled := e.(CancelErr); canceled {
				// do your cleanup here
			}
			panic(e) // re-panic with same error (cancel or not)
		}
	}()
	// your application code goes here
}

Index

Constants

View Source
const (
	HeaderContentType = "Content-Type"
	MediaJSON         = "application/json"
)

Variables

View Source
var (
	ErrForbidden     = errors.New("forbidden")
	ErrNoSuchProcess = errors.New("no such process")
)

Functions

func CheckCancel

func CheckCancel(id string)

CheckCancel introduces a cancellation point just like Status() does, but without changing the task status, nor adding an entry to history.

func DelAttribute

func DelAttribute(id, name string)

DelAttribute deletes an attribute for the task given by id. Unrecognized task identifiers are silently skipped.

func Done

func Done(id string)

Done marks the end of a task, writing to history depending on the outcome (i.e., aborted, killed or finished successfully). This function releases resources associated with the process, thus making the id available for use by another task. It also stops panics raising from cancellation requests, but only when the StopCancelPanic option is set AND Done is called with a defer statement.

func Kill

func Kill(id, message string) error

Kill sets a cancellation request to the task with the given identifier, that will be effective as soon as the routine running that task hits a cancellation point. The (optional) message will be included in the CancelErr object used for panic.

func ListenAndServe

func ListenAndServe(addr string) error

ListenAndServe starts an HTTP server at the given address (localhost:80 by default, as results from the underlying net/http implementation).

func SetAttribute

func SetAttribute(id, name string, value interface{})

SetAttribute sets an application-specific attribute for the task given by id. Unrecognized identifiers are silently skipped. Duplicate attribute names for the task overwrite the previously set value.

func SetOptions

func SetOptions(opts ProclistOpts)

SetOptions sets the options to be used for this Proclist.

func Start

func Start(id string, opts *ProcOpts, attrs *map[string]interface{})

Start marks the beginning of a task at the default Proclist. All attributes are recorded but not handled internally by the package. They will be mapped to JSON and provided to HTTP clients for this task. It's the caller's responsibility to provide different identifiers for separate tasks. An id can only be reused after the task previously using it is over. If process options are not provided (nil), Start() will snapshot the global options for the process list set by SetOptions().

func Status

func Status(id, status string)

Status changes the status for a task in the default Proclist, adding an item to the task's history. Note that Status() is a cancellation point, thus the routine calling it is subject to a panic due to a pending Kill().

Types

type CancelErr

type CancelErr string

Type CancelErr is the type used for cancellation-induced panics.

func (CancelErr) Error

func (e CancelErr) Error() string

Error returns the error message for a CancelErr.

type CancelRequest

type CancelRequest struct {
	Message string `json:"message"`
}

CancelRequest is the request body resulting from Kill().

type HistoryDetail

type HistoryDetail struct {
	Ts     time.Time `json:"ts"`
	Status string    `json:"status"`
}

HistoryDetail encodes one entry from the process' history.

type HistoryResponse

type HistoryResponse struct {
	History    []HistoryDetail `json:"history"`
	ServerTime time.Time       `json:"serverTime"`
}

HistoryResponse is the response for a GET to /proc/<id>/history.

type ProcDetail

type ProcDetail struct {
	Id         string                 `json:"id"`
	Attrs      map[string]interface{} `json:"attrs,omitempty"`
	ProcTime   time.Time              `json:"procTime"`
	StatusTime time.Time              `json:"statusTime"`
	Status     string                 `json:"status"`
	Cancelling bool                   `json:"cancelling,omitempty"`
}

Type ProcDetail encodes a full process list from the server, including an attributes array with application-defined names/values.

type ProcOpts

type ProcOpts struct {
	StopCancelPanic bool // Stop cancel-related panics at Done()
	ForbidCancel    bool // Forbid cancellation requests
}

Type ProcOpts provides options for the process.

type ProcResponse

type ProcResponse struct {
	Procs      []ProcDetail `json:"procs"`
	ServerTime time.Time    `json:"serverTime"`
}

ProcResponse is the response for a GET to /proc.

type Proclist

type Proclist struct {
	// contains filtered or unexported fields
}

Type Proclist is the main type for the process-list. You may have as many as you wish, each with it's own HTTP server, but that's probably useful only in a handful of cases. The typical use of this package is through the default Proclist object (DefaultProclist) and package-level functions. The zero value for the type is a Proclist ready to be used.

var DefaultProclist Proclist

This is the default Proclist set for the package. Package-level operations use this list.

func (*Proclist) CheckCancel

func (pl *Proclist) CheckCancel(id string)

CheckCancel introduces a cancellation point just like Status() does, but without changing the task status, nor adding an entry to history.

func (*Proclist) DelAttribute

func (pl *Proclist) DelAttribute(id, name string)

DelAttribute deletes an attribute for the task given by id. Unrecognized task identifiers are silently skipped.

func (*Proclist) Done

func (pl *Proclist) Done(id string)

Done marks the end of a task, writing in history depending on the outcome (i.e., aborted, killed or finished successfully). This function releases resources associated with the process, thus making the id available for use by another task. It also stops panics raising from cancellation requests, but only when the StopCancelPanic option is set AND Done is called with a defer statement.

func (*Proclist) Kill

func (pl *Proclist) Kill(id, message string) error

Kill sets a cancellation request to the task with the given identifier, that will be effective as soon as the routine running that task hits a cancellation point. The (optional) message will be included in the CancelErr object used for panic.

func (*Proclist) ListenAndServe

func (pl *Proclist) ListenAndServe(addr string) error

ListenAndServe starts an HTTP server at the given address (localhost:80 by default, as results from the underlying net/http implementation).

func (*Proclist) Options

func (pl *Proclist) Options() ProclistOpts

Options returns the options set for this Proclist.

func (*Proclist) SetAttribute

func (pl *Proclist) SetAttribute(id, name string, value interface{})

SetAttribute sets an application-specific attribute for the task given by id. Unrecognized identifiers are silently skipped. Duplicate attribute names for the task overwrite the previously set value.

func (*Proclist) SetOptions

func (pl *Proclist) SetOptions(opts ProclistOpts)

SetOptions sets the options to be used for this Proclist.

func (*Proclist) Start

func (pl *Proclist) Start(id string, opts *ProcOpts, attrs *map[string]interface{})

Start marks the beginning of a task at this Proclist. All attributes are recorded but not handled internally by the package. They will be mapped to JSON and provided to HTTP clients for this task. It's the caller's responsibility to provide different identifiers for separate tasks. An id can only be reused after the task previously using it is over. If process options are not provided (nil), Start() will snapshot the global options for the process list set by SetOptions().

func (*Proclist) Status

func (pl *Proclist) Status(id, status string)

Status changes the status for a task in a Proclist, adding an item to the task's history. Note that Status() is a cancellation point, thus the routine calling it is subject to a panic due to a pending Kill().

type ProclistOpts

type ProclistOpts struct {
	StopCancelPanic bool // Stop cancel-related panics at Done()
	ForbidCancel    bool // Forbid cancellation requests
}

Type ProclistOpts provides all options to be set for a Proclist. Options shared with ProcOpts act as defaults in case no options are provided in a task's call to Start().

func Options

func Options() ProclistOpts

Options returns the options set for the default Proclist.

Directories

Path Synopsis
This package provides an HTTP client to use with pm-enabled processes.
This package provides an HTTP client to use with pm-enabled processes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL