diff options
author | HJ <thechairman@thechairman.info> | 2024-02-03 15:10:00 -0500 |
---|---|---|
committer | HJ <thechairman@thechairman.info> | 2024-02-03 15:10:00 -0500 |
commit | 0c869b2782aeecb92dff232b46a499a3821f9f2c (patch) | |
tree | 8010032bf495ad120a4d586a7a96ebc9139e3f32 /aoc2023/build/packages/gleam_otp/src | |
parent | 96a3c5c179d8d3fff24eb2953e45f8dd15e2714c (diff) | |
download | gleam_aoc-0c869b2782aeecb92dff232b46a499a3821f9f2c.tar.gz gleam_aoc-0c869b2782aeecb92dff232b46a499a3821f9f2c.zip |
cleanup
Diffstat (limited to 'aoc2023/build/packages/gleam_otp/src')
16 files changed, 0 insertions, 2132 deletions
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/actor.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/actor.gleam deleted file mode 100644 index 9f6a6c4..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam/otp/actor.gleam +++ /dev/null @@ -1,504 +0,0 @@ -//// This module provides the _Actor_ abstraction, one of the most common -//// building blocks of Gleam OTP programs. -//// -//// An Actor is a process like any other BEAM process and can be be used to hold -//// state, execute code, and communicate with other processes by sending and -//// receiving messages. The advantage of using the actor abstraction over a bare -//// process is that it provides a single interface for commonly needed -//// functionality, including support for the [tracing and debugging -//// features in OTP](erlang-sys). -//// -//// Gleam's Actor is similar to Erlang's `gen_server` and Elixir's `GenServer` -//// but differs in that it offers a fully typed interface. This different API is -//// why Gleam uses the name Actor rather than some variation of generic-server. -//// -//// [erlang-sys]: https://www.erlang.org/doc/man/sys.html -//// -//// ## Example -//// -//// An Actor can be used to create a client-server interaction between an Actor -//// (the server) and other processes (the clients). In this example we have an -//// Actor that works as a stack, allowing clients to push and pop elements. -//// -//// ```gleam -//// pub fn main() { -//// // Start the actor with initial state of an empty list, and the -//// // `handle_message` callback function (defined below). -//// // We assert that it starts successfully. -//// // -//// // In real-world Gleam OTP programs we would likely write a wrapper functions -//// // called `start`, `push` `pop`, `shutdown` to start and interact with the -//// // Actor. We are not doing that here for the sake of showing how the Actor -//// // API works. -//// let assert Ok(actor) = actor.start([], handle_message) -//// -//// // We can send a message to the actor to push elements onto the stack. -//// process.send(actor, Push("Joe")) -//// process.send(actor, Push("Mike")) -//// process.send(actor, Push("Robert")) -//// -//// // The `Push` message expects no response, these messages are sent purely for -//// // the side effect of mutating the state held by the actor. -//// // -//// // We can also send the `Pop` message to take a value off of the actor's -//// // stack. This message expects a response, so we use `process.call` to send a -//// // message and wait until a reply is received. -//// // -//// // In this instance we are giving the actor 10 milliseconds to reply, if the -//// // `call` function doesn't get a reply within this time it will panic and -//// // crash the client process. -//// let assert Ok("Robert") = process.call(actor, Pop, 10) -//// let assert Ok("Mike") = process.call(actor, Pop, 10) -//// let assert Ok("Joe") = process.call(actor, Pop, 10) -//// -//// // The stack is now empty, so if we pop again the actor replies with an error. -//// let assert Error(Nil) = process.call(actor, Pop, 10) -//// -//// // Lastly, we can send a message to the actor asking it to shut down. -//// process.send(actor, Shutdown) -//// } -//// ``` -//// -//// Here is the code that is used to implement this actor: -//// -//// ```gleam -//// // First step of implementing the stack Actor is to define the message type that -//// // it can receive. -//// // -//// // The type of the elements in the stack is no fixed so a type parameter is used -//// // for it instead of a concrete type such as `String` or `Int`. -//// pub type Message(element) { -//// // The `Shutdown` message is used to tell the actor to stop. -//// // It is the simplest message type, it contains no data. -//// Shutdown -//// -//// // The `Push` message is used to add a new element to the stack. -//// // It contains the item to add, the type of which is the `element` -//// // parameterised type. -//// Push(push: element) -//// -//// // The `Pop` message is used to remove an element from the stack. -//// // It contains a `Subject`, which is used to send the response back to the -//// // message sender. In this case the reply is of type `Result(element, Nil)`. -//// Pop(reply_with: Subject(Result(element, Nil))) -//// } -//// -//// // The last part is to implement the `handle_message` callback function. -//// // -//// // This function is called by the Actor each for each message it receives. -//// // Actor is single threaded only does one thing at a time, so it handles -//// // messages sequentially and one at a time, in the order they are received. -//// // -//// // The function takes the message and the current state, and returns a data -//// // structure that indicates what to do next, along with the new state. -//// fn handle_message( -//// message: Message(e), -//// stack: List(e), -//// ) -> actor.Next(Message(e), List(e)) { -//// case message { -//// // For the `Shutdown` message we return the `actor.Stop` value, which causes -//// // the actor to discard any remaining messages and stop. -//// Shutdown -> actor.Stop(process.Normal) -//// -//// // For the `Push` message we add the new element to the stack and return -//// // `actor.continue` with this new stack, causing the actor to process any -//// // queued messages or wait for more. -//// Push(value) -> { -//// let new_state = [value, ..stack] -//// actor.continue(new_state) -//// } -//// -//// // For the `Pop` message we attempt to remove an element from the stack, -//// // sending it or an error back to the caller, before continuing. -//// Pop(client) -> -//// case stack { -//// [] -> { -//// // When the stack is empty we can't pop an element, so we send an -//// // error back. -//// process.send(client, Error(Nil)) -//// actor.continue([]) -//// } -//// -//// [first, ..rest] -> { -//// // Otherwise we send the first element back and use the remaining -//// // elements as the new state. -//// process.send(client, Ok(first)) -//// actor.continue(rest) -//// } -//// } -//// } -//// } -//// ``` - -import gleam/erlang/process.{ - type ExitReason, type Pid, type Selector, type Subject, Abnormal, -} -import gleam/erlang/charlist.{type Charlist} -import gleam/otp/system.{ - type DebugState, type Mode, type StatusInfo, type SystemMessage, GetState, - GetStatus, Resume, Running, StatusInfo, Suspend, Suspended, -} -import gleam/string -import gleam/dynamic.{type Dynamic} -import gleam/erlang/atom -import gleam/option.{type Option, None, Some} - -type Message(message) { - /// A regular message excepted by the process - Message(message) - - /// An OTP system message, for debugging or maintenance - System(SystemMessage) - - /// An unexpected message - Unexpected(Dynamic) -} - -/// The type used to indicate what to do after handling a message. -/// -pub type Next(message, state) { - /// Continue handling messages. - /// - Continue(state: state, selector: Option(Selector(message))) - - /// Stop handling messages and shut down. - /// - Stop(ExitReason) -} - -pub fn continue(state: state) -> Next(message, state) { - Continue(state, None) -} - -pub fn with_selector( - value: Next(message, state), - selector: Selector(message), -) -> Next(message, state) { - case value { - Continue(state, _) -> Continue(state, Some(selector)) - _ -> value - } -} - -/// The type used to indicate whether an actor has started successfully or not. -/// -pub type InitResult(state, message) { - /// The actor has successfully initialised. The actor can start handling - /// messages and actor's channel sender can be returned to the parent - /// process. - /// - Ready(state: state, selector: Selector(message)) - - /// The actor has failed to initialise. The actor shuts down and an error is - /// returned to the parent process. - /// - Failed(String) -} - -type Self(state, msg) { - Self( - mode: Mode, - parent: Pid, - state: state, - subject: Subject(msg), - selector: Selector(Message(msg)), - debug_state: DebugState, - message_handler: fn(msg, state) -> Next(msg, state), - ) -} - -/// This data structure holds all the values required by the `start_spec` -/// function in order to create an actor. -/// -/// If you do not need to configure the initialisation behaviour of your actor -/// consider using the `start` function. -/// -pub type Spec(state, msg) { - Spec( - /// The initialisation functionality for the actor. This function is called - /// just after the actor starts but before the channel sender is returned - /// to the parent. - /// - /// This function is used to ensure that any required data or state is - /// correct. If this function returns an error it means that the actor has - /// failed to start and an error is returned to the parent. - /// - init: fn() -> InitResult(state, msg), - /// How many milliseconds the `init` function has to return before it is - /// considered to have taken too long and failed. - /// - init_timeout: Int, - /// This function is called to handle each message that the actor receives. - /// - loop: fn(msg, state) -> Next(msg, state), - ) -} - -// TODO: Check needed functionality here to be OTP compatible -fn exit_process(reason: ExitReason) -> ExitReason { - // TODO - reason -} - -fn receive_message(self: Self(state, msg)) -> Message(msg) { - let selector = case self.mode { - // When suspended we only respond to system messages - Suspended -> - process.new_selector() - |> selecting_system_messages - - // When running we respond to all messages - Running -> - // We add the handler for unexpected messages first so that the user - // supplied selector can override it if desired - process.new_selector() - |> process.selecting_anything(Unexpected) - |> process.merge_selector(self.selector) - |> selecting_system_messages - } - - process.select_forever(selector) -} - -fn selecting_system_messages( - selector: Selector(Message(msg)), -) -> Selector(Message(msg)) { - selector - |> process.selecting_record3( - atom.create_from_string("system"), - convert_system_message, - ) -} - -@external(erlang, "gleam_otp_external", "convert_system_message") -fn convert_system_message(a: Dynamic, b: Dynamic) -> Message(msg) - -fn process_status_info(self: Self(state, msg)) -> StatusInfo { - StatusInfo( - module: atom.create_from_string("gleam@otp@actor"), - parent: self.parent, - mode: self.mode, - debug_state: self.debug_state, - state: dynamic.from(self.state), - ) -} - -fn loop(self: Self(state, msg)) -> ExitReason { - case receive_message(self) { - System(system) -> - case system { - GetState(callback) -> { - callback(dynamic.from(self.state)) - loop(self) - } - Resume(callback) -> { - callback() - loop(Self(..self, mode: Running)) - } - Suspend(callback) -> { - callback() - loop(Self(..self, mode: Suspended)) - } - GetStatus(callback) -> { - callback(process_status_info(self)) - loop(self) - } - } - - Unexpected(message) -> { - log_warning( - charlist.from_string("Actor discarding unexpected message: ~s"), - [charlist.from_string(string.inspect(message))], - ) - loop(self) - } - - Message(msg) -> - case self.message_handler(msg, self.state) { - Stop(reason) -> exit_process(reason) - Continue(state: state, selector: new_selector) -> { - let selector = - new_selector - |> option.map(init_selector(self.subject, _)) - |> option.unwrap(self.selector) - loop(Self(..self, state: state, selector: selector)) - } - } - } -} - -// TODO: replace this when we have Gleam bindings to the logger -@external(erlang, "logger", "warning") -fn log_warning(a: Charlist, b: List(Charlist)) -> Nil - -fn initialise_actor( - spec: Spec(state, msg), - ack: Subject(Result(Subject(msg), ExitReason)), -) { - let subject = process.new_subject() - case spec.init() { - Ready(state, selector) -> { - let selector = init_selector(subject, selector) - // Signal to parent that the process has initialised successfully - process.send(ack, Ok(subject)) - // Start message receive loop - let self = - Self( - state: state, - parent: process.subject_owner(ack), - subject: subject, - selector: selector, - message_handler: spec.loop, - debug_state: system.debug_state([]), - mode: Running, - ) - loop(self) - } - - Failed(reason) -> { - process.send(ack, Error(Abnormal(reason))) - exit_process(Abnormal(reason)) - } - } -} - -fn init_selector(subject, selector) { - process.new_selector() - |> process.selecting(subject, Message) - |> process.merge_selector(process.map_selector(selector, Message)) -} - -pub type StartError { - InitTimeout - InitFailed(ExitReason) - InitCrashed(Dynamic) -} - -/// The result of starting a Gleam actor. -/// -/// This type is compatible with Gleam supervisors. If you wish to convert it -/// to a type compatible with Erlang supervisors see the `ErlangStartResult` -/// type and `erlang_start_result` function. -/// -pub type StartResult(msg) = - Result(Subject(msg), StartError) - -/// An Erlang supervisor compatible process start result. -/// -/// If you wish to convert this into a `StartResult` compatible with Gleam -/// supervisors see the `from_erlang_start_result` and `wrap_erlang_starter` -/// functions. -/// -pub type ErlangStartResult = - Result(Pid, Dynamic) - -/// Convert a Gleam actor start result into an Erlang supervisor compatible -/// process start result. -/// -pub fn to_erlang_start_result(res: StartResult(msg)) -> ErlangStartResult { - case res { - Ok(x) -> Ok(process.subject_owner(x)) - Error(x) -> Error(dynamic.from(x)) - } -} - -type StartInitMessage(msg) { - Ack(Result(Subject(msg), ExitReason)) - Mon(process.ProcessDown) -} - -// TODO: test init_timeout. Currently if we test it eunit prints an error from -// the process death. How do we avoid this? -// -/// Start an actor from a given specification. If the actor's `init` function -/// returns an error or does not return within `init_timeout` then an error is -/// returned. -/// -/// If you do not need to specify the initialisation behaviour of your actor -/// consider using the `start` function. -/// -pub fn start_spec(spec: Spec(state, msg)) -> Result(Subject(msg), StartError) { - let ack_subject = process.new_subject() - - let child = - process.start( - linked: True, - running: fn() { initialise_actor(spec, ack_subject) }, - ) - - let monitor = process.monitor_process(child) - let selector = - process.new_selector() - |> process.selecting(ack_subject, Ack) - |> process.selecting_process_down(monitor, Mon) - - let result = case process.select(selector, spec.init_timeout) { - // Child started OK - Ok(Ack(Ok(channel))) -> Ok(channel) - - // Child initialiser returned an error - Ok(Ack(Error(reason))) -> Error(InitFailed(reason)) - - // Child went down while initialising - Ok(Mon(down)) -> Error(InitCrashed(down.reason)) - - // Child did not finish initialising in time - Error(Nil) -> { - process.kill(child) - Error(InitTimeout) - } - } - - // Remove the monitor used for the starting of the actor as to avoid an extra - // message arriving at the parent if the child dies later. - process.demonitor_process(monitor) - - result -} - -/// Start an actor with a given initial state and message handling loop -/// function. -/// -/// This function returns a `Result` but it will always be `Ok` so it is safe -/// to use with `assert` if you are not starting this actor as part of a -/// supervision tree. -/// -/// If you wish to configure the initialisation behaviour of a new actor see -/// the `Spec` record and the `start_spec` function. -/// -pub fn start( - state: state, - loop: fn(msg, state) -> Next(msg, state), -) -> Result(Subject(msg), StartError) { - start_spec(Spec( - init: fn() { Ready(state, process.new_selector()) }, - loop: loop, - init_timeout: 5000, - )) -} - -/// Send a message over a given channel. -/// -/// This is a re-export of `process.send`, for the sake of convenience. -/// -pub fn send(subject: Subject(msg), msg: msg) -> Nil { - process.send(subject, msg) -} - -// TODO: test -/// Send a synchronous message and wait for a response from the receiving -/// process. -/// -/// If a reply is not received within the given timeout then the sender process -/// crashes. If you wish receive a `Result` rather than crashing see the -/// `process.try_call` function. -/// -/// This is a re-export of `process.call`, for the sake of convenience. -/// -pub fn call( - selector: Subject(message), - make_message: fn(Subject(reply)) -> message, - timeout: Int, -) -> reply { - process.call(selector, make_message, timeout) -} diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/intensity_tracker.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/intensity_tracker.gleam deleted file mode 100644 index 2044be0..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam/otp/intensity_tracker.gleam +++ /dev/null @@ -1,46 +0,0 @@ -//// The intensity tracker is used to monitor how frequently an event happens, -//// erroring if it happens too many times within a period of time. - -import gleam/list - -// TODO: test -pub opaque type IntensityTracker { - IntensityTracker(limit: Int, period: Int, events: List(Int)) -} - -pub type TooIntense { - TooIntense -} - -pub fn new(limit limit: Int, period period: Int) -> IntensityTracker { - IntensityTracker(limit: limit, period: period, events: []) -} - -@external(erlang, "erlang", "monotonic_time") -fn monotonic_time(a: Int) -> Int - -fn now_seconds() -> Int { - monotonic_time(1) -} - -pub fn trim_window(events: List(Int), now: Int, period: Int) -> List(Int) { - case events { - [] -> [] - [event, ..events] -> - case now >= event + period { - True -> [event, ..trim_window(events, now, period)] - False -> [] - } - } -} - -pub fn add_event( - tracker: IntensityTracker, -) -> Result(IntensityTracker, TooIntense) { - let now = now_seconds() - let events = trim_window([now, ..tracker.events], now, tracker.period) - case list.length(events) >= tracker.limit { - True -> Error(TooIntense) - False -> Ok(IntensityTracker(..tracker, events: events)) - } -} diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/port.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/port.gleam deleted file mode 100644 index 4e1b4d8..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam/otp/port.gleam +++ /dev/null @@ -1,9 +0,0 @@ -/// Ports are how code running on the Erlang virtual machine interacts with -/// the outside world. Bytes of data can be sent to and read from ports, -/// providing a form of message passing to an external program or resource. -/// -/// For more information on ports see the [Erlang ports documentation][1]. -/// -/// [1]: https://erlang.org/doc/reference_manual/ports.html -/// -pub type Port diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/supervisor.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/supervisor.gleam deleted file mode 100644 index b99ad8e..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam/otp/supervisor.gleam +++ /dev/null @@ -1,410 +0,0 @@ -// TODO: specify amount of time permitted for shut-down -import gleam/result -import gleam/string -import gleam/option.{type Option, None, Some} -import gleam/erlang/process.{type Pid, type Subject} -import gleam/otp/actor.{type StartError} -import gleam/otp/intensity_tracker.{type IntensityTracker} -import gleam/erlang/node.{type Node} - -/// This data structure holds all the values required by the `start_spec` -/// function in order to create an supervisor. -/// -/// If you do not need to configure the behaviour of your supervisor consider -/// using the `start` function. -/// -pub type Spec(argument, return) { - Spec( - argument: argument, - max_frequency: Int, - frequency_period: Int, - init: fn(Children(argument)) -> Children(return), - ) -} - -/// This type represents the starting children of a supervisor within the -/// `init` function. -/// -pub opaque type Children(argument) { - Ready(Starter(argument)) - Failed(ChildStartError) -} - -/// This type contains all the information required to start a new child and -/// add it to the `Children`. -/// -/// This is typically created with the `worker` function. -/// -pub opaque type ChildSpec(msg, argument, returning) { - ChildSpec( - // TODO: merge this into one field - start: fn(argument) -> Result(Subject(msg), StartError), - returning: fn(argument, Subject(msg)) -> returning, - ) -} - -type ChildStartError { - ChildStartError(previous_pid: Option(Pid), error: StartError) -} - -pub opaque type Message { - Exit(process.ExitMessage) - RetryRestart(Pid) -} - -type Instruction { - StartAll - StartFrom(Pid) -} - -type State(a) { - State( - restarts: IntensityTracker, - starter: Starter(a), - retry_restarts: Subject(Pid), - ) -} - -type Starter(argument) { - Starter( - argument: argument, - exec: Option( - fn(Instruction) -> - Result(#(Starter(argument), Instruction), ChildStartError), - ), - ) -} - -type Child(argument) { - Child(pid: Pid, argument: argument) -} - -fn start_child( - child_spec: ChildSpec(msg, argument_in, argument_out), - argument: argument_in, -) -> Result(Child(argument_out), ChildStartError) { - use subject <- result.then( - child_spec.start(argument) - |> result.map_error(ChildStartError(None, _)), - ) - - Ok(Child( - pid: process.subject_owner(subject), - // Merge the new child's pid into the argument to produce the new argument - // used to start any remaining children. - argument: child_spec.returning(argument, subject), - )) -} - -// TODO: more sophsiticated stopping of processes. i.e. give supervisors -// more time to shut down. -fn shutdown_child(pid: Pid, _spec: ChildSpec(msg, arg_1, arg_2)) -> Nil { - process.send_exit(pid) -} - -fn perform_instruction_for_child( - argument: argument_in, - instruction: Instruction, - child_spec: ChildSpec(msg, argument_in, argument_out), - child: Child(argument_out), -) -> Result(#(Child(argument_out), Instruction), ChildStartError) { - let current = child.pid - case instruction { - // This child is older than the StartFrom target, we don't need to - // restart it - StartFrom(target) if target != current -> Ok(#(child, instruction)) - - // This pid either is the cause of the problem, or we have the StartAll - // instruction. Either way it and its younger siblings need to be restarted. - _ -> { - shutdown_child(current, child_spec) - use child <- result.then(start_child(child_spec, argument)) - Ok(#(child, StartAll)) - } - } -} - -fn add_child_to_starter( - starter: Starter(argument_in), - child_spec: ChildSpec(msg, argument_in, argument_out), - child: Child(argument_out), -) -> Starter(argument_out) { - let starter = fn(instruction) { - // Restart the older children. We use `try` to return early if the older - // children failed to start - use #(starter, instruction) <- result.then(case starter.exec { - Some(start) -> start(instruction) - None -> Ok(#(starter, instruction)) - }) - - // Perform the instruction, restarting the child as required - use #(child, instruction) <- result.then(perform_instruction_for_child( - starter.argument, - instruction, - child_spec, - child, - )) - - // Create a new starter for the next time the supervisor needs to restart - let starter = add_child_to_starter(starter, child_spec, child) - - Ok(#(starter, instruction)) - } - - Starter(exec: Some(starter), argument: child.argument) -} - -fn start_and_add_child( - state: Starter(argument_0), - child_spec: ChildSpec(msg, argument_0, argument_1), -) -> Children(argument_1) { - case start_child(child_spec, state.argument) { - Ok(child) -> Ready(add_child_to_starter(state, child_spec, child)) - Error(reason) -> Failed(reason) - } -} - -/// Add a child to the collection of children of the supervisor -/// -/// This function starts the child from the child spec. -/// -pub fn add( - children: Children(argument), - child_spec: ChildSpec(msg, argument, new_argument), -) -> Children(new_argument) { - case children { - // If one of the previous children has failed then we cannot continue - Failed(fail) -> Failed(fail) - - // If everything is OK so far then we can add the child - Ready(state) -> start_and_add_child(state, child_spec) - } -} - -// TODO: test -// TODO: unlimitd shut down duration -/// Prepare a new supervisor type child. -/// -/// If you wish to prepare a new non-supervisor type child see the `worker` -/// function. -/// -/// If you wish to change the type of the argument for later children see the -/// `returning` function. -/// -/// Note: Gleam supervisors do not yet support different shutdown periods per -/// child so this function is currently identical in behaviour to `worker`. It is -/// recommended to use this function for supervisor children nevertheless so the -/// correct shut down behaviour is used in later releases of this library. -/// -pub fn supervisor( - start: fn(argument) -> Result(Subject(msg), StartError), -) -> ChildSpec(msg, argument, argument) { - ChildSpec(start: start, returning: fn(argument, _channel) { argument }) -} - -/// Prepare a new worker type child. -/// -/// If you wish to prepare a new supervisor type child see the `supervisor` -/// function. -/// -/// If you wish to change the type of the argument for later children see the -/// `returning` function. -/// -pub fn worker( - start: fn(argument) -> Result(Subject(msg), StartError), -) -> ChildSpec(msg, argument, argument) { - ChildSpec(start: start, returning: fn(argument, _channel) { argument }) -} - -// TODO: test -/// As each child is added to a supervisors children a new argument is prepared -/// with which to start the next child. By default argument is the same as the -/// previous argument, but this function can be used to change it to something -/// else by passing a function that takes the previous argument and the sender -/// of the previous child. -/// -pub fn returning( - child: ChildSpec(msg, argument_a, argument_b), - updater: fn(argument_a, Subject(msg)) -> argument_c, -) -> ChildSpec(msg, argument_a, argument_c) { - ChildSpec(start: child.start, returning: updater) -} - -fn init( - spec: Spec(argument, return), -) -> actor.InitResult(State(return), Message) { - // Create a subject so that we can asynchronously retry restarting when we - // fail to bring an exited child - let retry = process.new_subject() - - // Trap exits so that we get a message when a child crashes - process.trap_exits(True) - - // Combine selectors - let selector = - process.new_selector() - |> process.selecting(retry, RetryRestart) - |> process.selecting_trapped_exits(Exit) - - // Start any children - let result = - Starter(argument: spec.argument, exec: None) - |> Ready - |> spec.init - - // Pass back up the result - case result { - Ready(starter) -> { - let restarts = - intensity_tracker.new( - limit: spec.max_frequency, - period: spec.frequency_period, - ) - let state = - State(starter: starter, restarts: restarts, retry_restarts: retry) - actor.Ready(state, selector) - } - - Failed(error) -> - actor.Failed(case error.error { - actor.InitTimeout -> "Child initialisation timed out" - actor.InitCrashed(reason) -> - string.append( - "Child crashed during initialisation: ", - string.inspect(reason), - ) - actor.InitFailed(reason) -> - string.append( - "Child failed to start during initialisation: ", - string.inspect(reason), - ) - }) - } -} - -type HandleExitError { - RestartFailed(pid: Pid, restarts: IntensityTracker) - TooManyRestarts -} - -fn handle_exit(pid: Pid, state: State(a)) -> actor.Next(Message, State(a)) { - let outcome = { - // If we are handling an exit then we must have some children - let assert Some(start) = state.starter.exec - - // Check to see if there has been too many restarts in this period - use restarts <- result.then( - state.restarts - |> intensity_tracker.add_event - |> result.map_error(fn(_) { TooManyRestarts }), - ) - - // Restart the exited child and any following children - use #(starter, _) <- result.then( - start(StartFrom(pid)) - |> result.map_error(fn(e: ChildStartError) { - RestartFailed(option.unwrap(e.previous_pid, pid), restarts) - }), - ) - - Ok(State(..state, starter: starter, restarts: restarts)) - } - - case outcome { - Ok(state) -> actor.continue(state) - Error(RestartFailed(failed_child, restarts)) -> { - // Asynchronously enqueue the restarting of this child again as we were - // unable to restart them this time. We do this asynchronously as we want - // to have a chance to handle any system messages that have come in. - process.send(state.retry_restarts, failed_child) - let state = State(..state, restarts: restarts) - actor.continue(state) - } - Error(TooManyRestarts) -> - actor.Stop(process.Abnormal( - "Child processes restarted too many times within allowed period", - )) - } -} - -fn loop( - message: Message, - state: State(argument), -) -> actor.Next(Message, State(argument)) { - case message { - Exit(exit_message) -> handle_exit(exit_message.pid, state) - RetryRestart(pid) -> handle_exit(pid, state) - } -} - -/// Start a supervisor from a given specification. -/// -pub fn start_spec(spec: Spec(a, b)) -> Result(Subject(Message), StartError) { - actor.start_spec(actor.Spec( - init: fn() { init(spec) }, - loop: loop, - init_timeout: 60_000, - )) -} - -/// Start a supervisor from a given `init` function. -/// -/// The init argument passed to children will be `Nil` and the maximum restart -/// intensity will be 1 restart per 5 seconds (the same as the default for -/// [Erlang supervisors][erl-sup]). If you wish to specify these values, see -/// the `start_spec` function and the `Spec` type. -/// -/// [erl-sup]: https://www.erlang.org/doc/design_principles/sup_princ.html#maximum-restart-intensity -/// -pub fn start( - init: fn(Children(Nil)) -> Children(a), -) -> Result(Subject(Message), StartError) { - start_spec(Spec( - init: init, - argument: Nil, - max_frequency: 1, - frequency_period: 5, - )) -} - -/// A type used to describe the situation in which an Erlang based application -/// is starting. -/// -/// For more information see the [Erlang distributed application -/// documentation][1] and the Learn Your Some Erlang chapter on [distributed -/// applications][2]. -/// -/// [1]: https://erlang.org/doc/design_principles/distributed_applications.html -/// [2]: https://learnyousomeerlang.com/distributed-otp-applications -/// -pub type ApplicationStartMode { - Normal - Takeover(Node) - Failover(Node) -} - -pub type ApplicationStop - -@external(erlang, "gleam_otp_external", "application_stopped") -pub fn application_stopped() -> ApplicationStop - -/// The result of starting a Gleam actor. -/// -/// This type is compatible with Gleam supervisors. If you wish to convert it -/// to a type compatible with Erlang supervisors see the `ErlangStartResult` -/// type and `erlang_start_result` function. -/// -pub type StartResult(msg) = - actor.StartResult(msg) - -/// An Erlang supervisor compatible process start result. -/// -pub type ErlangStartResult = - actor.ErlangStartResult - -/// Convert a Gleam actor start result into an Erlang supervisor compatible -/// process start result. -/// -pub fn to_erlang_start_result(res: StartResult(msg)) -> ErlangStartResult { - actor.to_erlang_start_result(res) -} diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/system.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/system.gleam deleted file mode 100644 index c05646b..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam/otp/system.gleam +++ /dev/null @@ -1,89 +0,0 @@ -import gleam/dynamic.{type Dynamic} -import gleam/erlang/atom.{type Atom} -import gleam/erlang/process.{type Pid} - -pub type Mode { - Running - Suspended -} - -pub type DebugOption { - NoDebug -} - -pub type DebugState - -@external(erlang, "sys", "debug_options") -pub fn debug_state(a: List(DebugOption)) -> DebugState - -pub type StatusInfo { - StatusInfo( - module: Atom, - parent: Pid, - mode: Mode, - debug_state: DebugState, - state: Dynamic, - ) -} - -// TODO: document -// TODO: implement remaining messages -pub type SystemMessage { - // {replace_state, StateFn} - // {change_code, Mod, Vsn, Extra} - // {terminate, Reason} - // {debug, {log, Flag}} - // {debug, {trace, Flag}} - // {debug, {log_to_file, FileName}} - // {debug, {statistics, Flag}} - // {debug, no_debug} - // {debug, {install, {Func, FuncState}}} - // {debug, {install, {FuncId, Func, FuncState}}} - // {debug, {remove, FuncOrId}} - Resume(fn() -> Nil) - Suspend(fn() -> Nil) - GetState(fn(Dynamic) -> Nil) - GetStatus(fn(StatusInfo) -> Nil) -} - -type DoNotLeak - -/// Get the state of a given OTP compatible process. This function is only -/// intended for debugging. -/// -/// For more information see the [Erlang documentation][1]. -/// -/// [1]: https://erlang.org/doc/man/sys.html#get_state-1 -/// -@external(erlang, "sys", "get_state") -pub fn get_state(from from: Pid) -> Dynamic - -@external(erlang, "sys", "suspend") -fn erl_suspend(a: Pid) -> DoNotLeak - -/// Request an OTP compatible process to suspend, causing it to only handle -/// system messages. -/// -/// For more information see the [Erlang documentation][1]. -/// -/// [1]: https://erlang.org/doc/man/sys.html#suspend-1 -/// -pub fn suspend(pid: Pid) -> Nil { - erl_suspend(pid) - Nil -} - -@external(erlang, "sys", "resume") -fn erl_resume(from from: Pid) -> DoNotLeak - -/// Request a suspended OTP compatible process to result, causing it to handle -/// all messages rather than only system messages. -/// -/// For more information see the [Erlang documentation][1]. -/// -/// [1]: https://erlang.org/doc/man/sys.html#resume-1 -/// -pub fn resume(pid: Pid) -> Nil { - erl_resume(pid) - Nil -} diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/task.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/task.gleam deleted file mode 100644 index b2b2c5c..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam/otp/task.gleam +++ /dev/null @@ -1,151 +0,0 @@ -//// A task is a kind of process that performs a single task and then shuts -//// down. Commonly tasks are used to convert sequential code into concurrent -//// code by performing computation in another process. -//// -//// let task = task.async(fn() { do_some_work() }) -//// let value = do_some_other_work() -//// value + task.await(task, 100) -//// -//// Tasks spawned with async can be awaited on by their caller process (and -//// only their caller) as shown in the example above. They are implemented by -//// spawning a process that sends a message to the caller once the given -//// computation is performed. -//// -//// There are two important things to consider when using `async`: -//// -//// 1. If you are using async tasks, you must await a reply as they are always -//// sent. -//// -//// 2. async tasks link the caller and the spawned process. This means that, -//// if the caller crashes, the task will crash too and vice-versa. This is -//// on purpose: if the process meant to receive the result no longer -//// exists, there is no purpose in completing the computation. -//// -//// This module is inspired by Elixir's [Task module][1]. -//// -//// [1]: https://hexdocs.pm/elixir/master/Task.html -//// - -// TODO: await_many -import gleam/erlang/process.{type Pid, type ProcessMonitor, type Selector} -import gleam/dynamic.{type Dynamic} - -pub opaque type Task(value) { - Task( - owner: Pid, - pid: Pid, - monitor: ProcessMonitor, - selector: Selector(Message(value)), - ) -} - -// TODO: test -/// Spawn a task process that calls a given function in order to perform some -/// work. The result of this function is send back to the parent and can be -/// received using the `await` function. -/// -/// See the top level module documentation for more information on async/await. -/// -pub fn async(work: fn() -> value) -> Task(value) { - let owner = process.self() - let subject = process.new_subject() - let pid = - process.start(linked: True, running: fn() { process.send(subject, work()) }) - let monitor = process.monitor_process(pid) - let selector = - process.new_selector() - |> process.selecting_process_down(monitor, FromMonitor) - |> process.selecting(subject, FromSubject) - Task(owner: owner, pid: pid, monitor: monitor, selector: selector) -} - -pub type AwaitError { - Timeout - Exit(reason: Dynamic) -} - -// We can only wait on a task if we are the owner of it so crash if we are -// waiting on a task we don't own. -fn assert_owner(task: Task(a)) -> Nil { - let self = process.self() - case task.owner == self { - True -> Nil - False -> - process.send_abnormal_exit( - self, - "awaited on a task that does not belong to this process", - ) - } -} - -type Message(value) { - FromMonitor(process.ProcessDown) - FromSubject(value) -} - -// TODO: test -/// Wait for the value computed by a task. -/// -/// If the a value is not received before the timeout has elapsed or if the -/// task process crashes then an error is returned. -/// -pub fn try_await(task: Task(value), timeout: Int) -> Result(value, AwaitError) { - assert_owner(task) - case process.select(task.selector, timeout) { - // The task process has sent back a value - Ok(FromSubject(x)) -> { - process.demonitor_process(task.monitor) - Ok(x) - } - - // The task process crashed without sending a value - Ok(FromMonitor(process.ProcessDown(reason: reason, ..))) -> - Error(Exit(reason)) - - // The task process is alive but has not sent a value yet - Error(Nil) -> Error(Timeout) - } -} - -// TODO: test -/// Wait for the value computed by a task. -/// -/// If the a value is not received before the timeout has elapsed or if the -/// task process crashes then this function crashes. -/// -pub fn await(task: Task(value), timeout: Int) -> value { - let assert Ok(value) = try_await(task, timeout) - value -} - -/// Wait endlessly for the value computed by a task. -/// -/// Be Careful! This function does not return until there is a value to -/// receive. If a value is not received then the process will be stuck waiting -/// forever. -/// -pub fn try_await_forever(task: Task(value)) -> Result(value, AwaitError) { - assert_owner(task) - case process.select_forever(task.selector) { - // The task process has sent back a value - FromSubject(x) -> { - process.demonitor_process(task.monitor) - Ok(x) - } - - // The task process crashed without sending a value - FromMonitor(process.ProcessDown(reason: reason, ..)) -> Error(Exit(reason)) - } -} - -/// Wait endlessly for the value computed by a task. -/// -/// Be Careful! Like `try_await_forever`, this function does not return until there is a value to -/// receive. -/// -/// If the task process crashes then this function crashes. -/// -pub fn await_forever(task: Task(value)) -> value { - let assert Ok(value) = try_await_forever(task) - value -} diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@actor.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@actor.erl deleted file mode 100644 index 0606147..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam@otp@actor.erl +++ /dev/null @@ -1,273 +0,0 @@ --module(gleam@otp@actor). --compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). - --export([continue/1, with_selector/2, to_erlang_start_result/1, start_spec/1, start/2, send/2, call/3]). --export_type([message/1, next/2, init_result/2, self/2, spec/2, start_error/0, start_init_message/1]). - --type message(GAS) :: {message, GAS} | - {system, gleam@otp@system:system_message()} | - {unexpected, gleam@dynamic:dynamic_()}. - --type next(GAT, GAU) :: {continue, - GAU, - gleam@option:option(gleam@erlang@process:selector(GAT))} | - {stop, gleam@erlang@process:exit_reason()}. - --type init_result(GAV, GAW) :: {ready, GAV, gleam@erlang@process:selector(GAW)} | - {failed, binary()}. - --type self(GAX, GAY) :: {self, - gleam@otp@system:mode(), - gleam@erlang@process:pid_(), - GAX, - gleam@erlang@process:subject(GAY), - gleam@erlang@process:selector(message(GAY)), - gleam@otp@system:debug_state(), - fun((GAY, GAX) -> next(GAY, GAX))}. - --type spec(GAZ, GBA) :: {spec, - fun(() -> init_result(GAZ, GBA)), - integer(), - fun((GBA, GAZ) -> next(GBA, GAZ))}. - --type start_error() :: init_timeout | - {init_failed, gleam@erlang@process:exit_reason()} | - {init_crashed, gleam@dynamic:dynamic_()}. - --type start_init_message(GBB) :: {ack, - {ok, gleam@erlang@process:subject(GBB)} | - {error, gleam@erlang@process:exit_reason()}} | - {mon, gleam@erlang@process:process_down()}. - --spec continue(GBI) -> next(any(), GBI). -continue(State) -> - {continue, State, none}. - --spec with_selector(next(GBM, GBN), gleam@erlang@process:selector(GBM)) -> next(GBM, GBN). -with_selector(Value, Selector) -> - case Value of - {continue, State, _} -> - {continue, State, {some, Selector}}; - - _ -> - Value - end. - --spec exit_process(gleam@erlang@process:exit_reason()) -> gleam@erlang@process:exit_reason(). -exit_process(Reason) -> - Reason. - --spec selecting_system_messages(gleam@erlang@process:selector(message(GBY))) -> gleam@erlang@process:selector(message(GBY)). -selecting_system_messages(Selector) -> - _pipe = Selector, - gleam@erlang@process:selecting_record3( - _pipe, - erlang:binary_to_atom(<<"system"/utf8>>), - fun gleam_otp_external:convert_system_message/2 - ). - --spec receive_message(self(any(), GBU)) -> message(GBU). -receive_message(Self) -> - Selector = case erlang:element(2, Self) of - suspended -> - _pipe = gleam_erlang_ffi:new_selector(), - selecting_system_messages(_pipe); - - running -> - _pipe@1 = gleam_erlang_ffi:new_selector(), - _pipe@2 = gleam@erlang@process:selecting_anything( - _pipe@1, - fun(Field@0) -> {unexpected, Field@0} end - ), - _pipe@3 = gleam_erlang_ffi:merge_selector( - _pipe@2, - erlang:element(6, Self) - ), - selecting_system_messages(_pipe@3) - end, - gleam_erlang_ffi:select(Selector). - --spec process_status_info(self(any(), any())) -> gleam@otp@system:status_info(). -process_status_info(Self) -> - {status_info, - erlang:binary_to_atom(<<"gleam@otp@actor"/utf8>>), - erlang:element(3, Self), - erlang:element(2, Self), - erlang:element(7, Self), - gleam@dynamic:from(erlang:element(4, Self))}. - --spec init_selector( - gleam@erlang@process:subject(GGN), - gleam@erlang@process:selector(GGN) -) -> gleam@erlang@process:selector(message(GGN)). -init_selector(Subject, Selector) -> - _pipe = gleam_erlang_ffi:new_selector(), - _pipe@1 = gleam@erlang@process:selecting( - _pipe, - Subject, - fun(Field@0) -> {message, Field@0} end - ), - gleam_erlang_ffi:merge_selector( - _pipe@1, - gleam_erlang_ffi:map_selector( - Selector, - fun(Field@0) -> {message, Field@0} end - ) - ). - --spec loop(self(any(), any())) -> gleam@erlang@process:exit_reason(). -loop(Self) -> - case receive_message(Self) of - {system, System} -> - case System of - {get_state, Callback} -> - Callback(gleam@dynamic:from(erlang:element(4, Self))), - loop(Self); - - {resume, Callback@1} -> - Callback@1(), - loop(erlang:setelement(2, Self, running)); - - {suspend, Callback@2} -> - Callback@2(), - loop(erlang:setelement(2, Self, suspended)); - - {get_status, Callback@3} -> - Callback@3(process_status_info(Self)), - loop(Self) - end; - - {unexpected, Message} -> - logger:warning( - unicode:characters_to_list( - <<"Actor discarding unexpected message: ~s"/utf8>> - ), - [unicode:characters_to_list(gleam@string:inspect(Message))] - ), - loop(Self); - - {message, Msg} -> - case (erlang:element(8, Self))(Msg, erlang:element(4, Self)) of - {stop, Reason} -> - exit_process(Reason); - - {continue, State, New_selector} -> - Selector = begin - _pipe = New_selector, - _pipe@1 = gleam@option:map( - _pipe, - fun(_capture) -> - init_selector(erlang:element(5, Self), _capture) - end - ), - gleam@option:unwrap(_pipe@1, erlang:element(6, Self)) - end, - loop( - erlang:setelement( - 6, - erlang:setelement(4, Self, State), - Selector - ) - ) - end - end. - --spec initialise_actor( - spec(any(), GCP), - gleam@erlang@process:subject({ok, gleam@erlang@process:subject(GCP)} | - {error, gleam@erlang@process:exit_reason()}) -) -> gleam@erlang@process:exit_reason(). -initialise_actor(Spec, Ack) -> - Subject = gleam@erlang@process:new_subject(), - case (erlang:element(2, Spec))() of - {ready, State, Selector} -> - Selector@1 = init_selector(Subject, Selector), - gleam@erlang@process:send(Ack, {ok, Subject}), - Self = {self, - running, - gleam@erlang@process:subject_owner(Ack), - State, - Subject, - Selector@1, - sys:debug_options([]), - erlang:element(4, Spec)}, - loop(Self); - - {failed, Reason} -> - gleam@erlang@process:send(Ack, {error, {abnormal, Reason}}), - exit_process({abnormal, Reason}) - end. - --spec to_erlang_start_result( - {ok, gleam@erlang@process:subject(any())} | {error, start_error()} -) -> {ok, gleam@erlang@process:pid_()} | {error, gleam@dynamic:dynamic_()}. -to_erlang_start_result(Res) -> - case Res of - {ok, X} -> - {ok, gleam@erlang@process:subject_owner(X)}; - - {error, X@1} -> - {error, gleam@dynamic:from(X@1)} - end. - --spec start_spec(spec(any(), GDD)) -> {ok, gleam@erlang@process:subject(GDD)} | - {error, start_error()}. -start_spec(Spec) -> - Ack_subject = gleam@erlang@process:new_subject(), - Child = gleam@erlang@process:start( - fun() -> initialise_actor(Spec, Ack_subject) end, - true - ), - Monitor = gleam@erlang@process:monitor_process(Child), - Selector = begin - _pipe = gleam_erlang_ffi:new_selector(), - _pipe@1 = gleam@erlang@process:selecting( - _pipe, - Ack_subject, - fun(Field@0) -> {ack, Field@0} end - ), - gleam@erlang@process:selecting_process_down( - _pipe@1, - Monitor, - fun(Field@0) -> {mon, Field@0} end - ) - end, - Result = case gleam_erlang_ffi:select(Selector, erlang:element(3, Spec)) of - {ok, {ack, {ok, Channel}}} -> - {ok, Channel}; - - {ok, {ack, {error, Reason}}} -> - {error, {init_failed, Reason}}; - - {ok, {mon, Down}} -> - {error, {init_crashed, erlang:element(3, Down)}}; - - {error, nil} -> - gleam@erlang@process:kill(Child), - {error, init_timeout} - end, - gleam_erlang_ffi:demonitor(Monitor), - Result. - --spec start(GDJ, fun((GDK, GDJ) -> next(GDK, GDJ))) -> {ok, - gleam@erlang@process:subject(GDK)} | - {error, start_error()}. -start(State, Loop) -> - start_spec( - {spec, - fun() -> {ready, State, gleam_erlang_ffi:new_selector()} end, - 5000, - Loop} - ). - --spec send(gleam@erlang@process:subject(GDQ), GDQ) -> nil. -send(Subject, Msg) -> - gleam@erlang@process:send(Subject, Msg). - --spec call( - gleam@erlang@process:subject(GDS), - fun((gleam@erlang@process:subject(GDU)) -> GDS), - integer() -) -> GDU. -call(Selector, Make_message, Timeout) -> - gleam@erlang@process:call(Selector, Make_message, Timeout). diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@intensity_tracker.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@intensity_tracker.erl deleted file mode 100644 index 8792f14..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam@otp@intensity_tracker.erl +++ /dev/null @@ -1,53 +0,0 @@ --module(gleam@otp@intensity_tracker). --compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). - --export([new/2, trim_window/3, add_event/1]). --export_type([intensity_tracker/0, too_intense/0]). - --opaque intensity_tracker() :: {intensity_tracker, - integer(), - integer(), - list(integer())}. - --type too_intense() :: too_intense. - --spec new(integer(), integer()) -> intensity_tracker(). -new(Limit, Period) -> - {intensity_tracker, Limit, Period, []}. - --spec now_seconds() -> integer(). -now_seconds() -> - erlang:monotonic_time(1). - --spec trim_window(list(integer()), integer(), integer()) -> list(integer()). -trim_window(Events, Now, Period) -> - case Events of - [] -> - []; - - [Event | Events@1] -> - case Now >= (Event + Period) of - true -> - [Event | trim_window(Events@1, Now, Period)]; - - false -> - [] - end - end. - --spec add_event(intensity_tracker()) -> {ok, intensity_tracker()} | - {error, too_intense()}. -add_event(Tracker) -> - Now = now_seconds(), - Events = trim_window( - [Now | erlang:element(4, Tracker)], - Now, - erlang:element(3, Tracker) - ), - case gleam@list:length(Events) >= erlang:element(2, Tracker) of - true -> - {error, too_intense}; - - false -> - {ok, erlang:setelement(4, Tracker, Events)} - end. diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@port.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@port.erl deleted file mode 100644 index b205739..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam@otp@port.erl +++ /dev/null @@ -1,8 +0,0 @@ --module(gleam@otp@port). --compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). - --export_type([port_/0]). - --type port_() :: any(). - - diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@supervisor.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@supervisor.erl deleted file mode 100644 index 39118f1..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam@otp@supervisor.erl +++ /dev/null @@ -1,322 +0,0 @@ --module(gleam@otp@supervisor). --compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). - --export([add/2, supervisor/1, worker/1, returning/2, start_spec/1, start/1, application_stopped/0, to_erlang_start_result/1]). --export_type([spec/2, children/1, child_spec/3, child_start_error/0, message/0, instruction/0, state/1, starter/1, child/1, handle_exit_error/0, application_start_mode/0, application_stop/0]). - --type spec(GLS, GLT) :: {spec, - GLS, - integer(), - integer(), - fun((children(GLS)) -> children(GLT))}. - --opaque children(GLU) :: {ready, starter(GLU)} | {failed, child_start_error()}. - --opaque child_spec(GLV, GLW, GLX) :: {child_spec, - fun((GLW) -> {ok, gleam@erlang@process:subject(GLV)} | - {error, gleam@otp@actor:start_error()}), - fun((GLW, gleam@erlang@process:subject(GLV)) -> GLX)}. - --type child_start_error() :: {child_start_error, - gleam@option:option(gleam@erlang@process:pid_()), - gleam@otp@actor:start_error()}. - --opaque message() :: {exit, gleam@erlang@process:exit_message()} | - {retry_restart, gleam@erlang@process:pid_()}. - --type instruction() :: start_all | {start_from, gleam@erlang@process:pid_()}. - --type state(GLY) :: {state, - gleam@otp@intensity_tracker:intensity_tracker(), - starter(GLY), - gleam@erlang@process:subject(gleam@erlang@process:pid_())}. - --type starter(GLZ) :: {starter, - GLZ, - gleam@option:option(fun((instruction()) -> {ok, - {starter(GLZ), instruction()}} | - {error, child_start_error()}))}. - --type child(GMA) :: {child, gleam@erlang@process:pid_(), GMA}. - --type handle_exit_error() :: {restart_failed, - gleam@erlang@process:pid_(), - gleam@otp@intensity_tracker:intensity_tracker()} | - too_many_restarts. - --type application_start_mode() :: normal | - {takeover, gleam@erlang@node:node_()} | - {failover, gleam@erlang@node:node_()}. - --type application_stop() :: any(). - --spec start_child(child_spec(any(), GME, GMF), GME) -> {ok, child(GMF)} | - {error, child_start_error()}. -start_child(Child_spec, Argument) -> - gleam@result:then( - begin - _pipe = (erlang:element(2, Child_spec))(Argument), - gleam@result:map_error( - _pipe, - fun(_capture) -> {child_start_error, none, _capture} end - ) - end, - fun(Subject) -> - {ok, - {child, - gleam@erlang@process:subject_owner(Subject), - (erlang:element(3, Child_spec))(Argument, Subject)}} - end - ). - --spec shutdown_child( - gleam@erlang@process:pid_(), - child_spec(any(), any(), any()) -) -> nil. -shutdown_child(Pid, _) -> - gleam@erlang@process:send_exit(Pid). - --spec perform_instruction_for_child( - GMS, - instruction(), - child_spec(any(), GMS, GMU), - child(GMU) -) -> {ok, {child(GMU), instruction()}} | {error, child_start_error()}. -perform_instruction_for_child(Argument, Instruction, Child_spec, Child) -> - Current = erlang:element(2, Child), - case Instruction of - {start_from, Target} when Target =/= Current -> - {ok, {Child, Instruction}}; - - _ -> - shutdown_child(Current, Child_spec), - gleam@result:then( - start_child(Child_spec, Argument), - fun(Child@1) -> {ok, {Child@1, start_all}} end - ) - end. - --spec add_child_to_starter( - starter(GNC), - child_spec(any(), GNC, GNF), - child(GNF) -) -> starter(GNF). -add_child_to_starter(Starter, Child_spec, Child) -> - Starter@3 = fun(Instruction) -> - gleam@result:then(case erlang:element(3, Starter) of - {some, Start} -> - Start(Instruction); - - none -> - {ok, {Starter, Instruction}} - end, fun(_use0) -> - {Starter@1, Instruction@1} = _use0, - gleam@result:then( - perform_instruction_for_child( - erlang:element(2, Starter@1), - Instruction@1, - Child_spec, - Child - ), - fun(_use0@1) -> - {Child@1, Instruction@2} = _use0@1, - Starter@2 = add_child_to_starter( - Starter@1, - Child_spec, - Child@1 - ), - {ok, {Starter@2, Instruction@2}} - end - ) - end) - end, - {starter, erlang:element(3, Child), {some, Starter@3}}. - --spec start_and_add_child(starter(GNL), child_spec(any(), GNL, GNO)) -> children(GNO). -start_and_add_child(State, Child_spec) -> - case start_child(Child_spec, erlang:element(2, State)) of - {ok, Child} -> - {ready, add_child_to_starter(State, Child_spec, Child)}; - - {error, Reason} -> - {failed, Reason} - end. - --spec add(children(GNT), child_spec(any(), GNT, GNW)) -> children(GNW). -add(Children, Child_spec) -> - case Children of - {failed, Fail} -> - {failed, Fail}; - - {ready, State} -> - start_and_add_child(State, Child_spec) - end. - --spec supervisor( - fun((GOB) -> {ok, gleam@erlang@process:subject(GOC)} | - {error, gleam@otp@actor:start_error()}) -) -> child_spec(GOC, GOB, GOB). -supervisor(Start) -> - {child_spec, Start, fun(Argument, _) -> Argument end}. - --spec worker( - fun((GOJ) -> {ok, gleam@erlang@process:subject(GOK)} | - {error, gleam@otp@actor:start_error()}) -) -> child_spec(GOK, GOJ, GOJ). -worker(Start) -> - {child_spec, Start, fun(Argument, _) -> Argument end}. - --spec returning( - child_spec(GOR, GOS, any()), - fun((GOS, gleam@erlang@process:subject(GOR)) -> GOY) -) -> child_spec(GOR, GOS, GOY). -returning(Child, Updater) -> - {child_spec, erlang:element(2, Child), Updater}. - --spec init(spec(any(), GPD)) -> gleam@otp@actor:init_result(state(GPD), message()). -init(Spec) -> - Retry = gleam@erlang@process:new_subject(), - gleam_erlang_ffi:trap_exits(true), - Selector = begin - _pipe = gleam_erlang_ffi:new_selector(), - _pipe@1 = gleam@erlang@process:selecting( - _pipe, - Retry, - fun(Field@0) -> {retry_restart, Field@0} end - ), - gleam@erlang@process:selecting_trapped_exits( - _pipe@1, - fun(Field@0) -> {exit, Field@0} end - ) - end, - Result = begin - _pipe@2 = {starter, erlang:element(2, Spec), none}, - _pipe@3 = {ready, _pipe@2}, - (erlang:element(5, Spec))(_pipe@3) - end, - case Result of - {ready, Starter} -> - Restarts = gleam@otp@intensity_tracker:new( - erlang:element(3, Spec), - erlang:element(4, Spec) - ), - State = {state, Restarts, Starter, Retry}, - {ready, State, Selector}; - - {failed, Error} -> - {failed, case erlang:element(3, Error) of - init_timeout -> - <<"Child initialisation timed out"/utf8>>; - - {init_crashed, Reason} -> - gleam@string:append( - <<"Child crashed during initialisation: "/utf8>>, - gleam@string:inspect(Reason) - ); - - {init_failed, Reason@1} -> - gleam@string:append( - <<"Child failed to start during initialisation: "/utf8>>, - gleam@string:inspect(Reason@1) - ) - end} - end. - --spec handle_exit(gleam@erlang@process:pid_(), state(GPJ)) -> gleam@otp@actor:next(message(), state(GPJ)). -handle_exit(Pid, State) -> - Outcome = begin - _assert_subject = erlang:element(3, erlang:element(3, State)), - {some, Start} = case _assert_subject of - {some, _} -> _assert_subject; - _assert_fail -> - erlang:error(#{gleam_error => let_assert, - message => <<"Assertion pattern match failed"/utf8>>, - value => _assert_fail, - module => <<"gleam/otp/supervisor"/utf8>>, - function => <<"handle_exit"/utf8>>, - line => 293}) - end, - gleam@result:then( - begin - _pipe = erlang:element(2, State), - _pipe@1 = gleam@otp@intensity_tracker:add_event(_pipe), - gleam@result:map_error(_pipe@1, fun(_) -> too_many_restarts end) - end, - fun(Restarts) -> - gleam@result:then( - begin - _pipe@2 = Start({start_from, Pid}), - gleam@result:map_error( - _pipe@2, - fun(E) -> - {restart_failed, - gleam@option:unwrap( - erlang:element(2, E), - Pid - ), - Restarts} - end - ) - end, - fun(_use0) -> - {Starter, _} = _use0, - {ok, - erlang:setelement( - 2, - erlang:setelement(3, State, Starter), - Restarts - )} - end - ) - end - ) - end, - case Outcome of - {ok, State@1} -> - gleam@otp@actor:continue(State@1); - - {error, {restart_failed, Failed_child, Restarts@1}} -> - gleam@erlang@process:send(erlang:element(4, State), Failed_child), - State@2 = erlang:setelement(2, State, Restarts@1), - gleam@otp@actor:continue(State@2); - - {error, too_many_restarts} -> - {stop, - {abnormal, - <<"Child processes restarted too many times within allowed period"/utf8>>}} - end. - --spec loop(message(), state(GPO)) -> gleam@otp@actor:next(message(), state(GPO)). -loop(Message, State) -> - case Message of - {exit, Exit_message} -> - handle_exit(erlang:element(2, Exit_message), State); - - {retry_restart, Pid} -> - handle_exit(Pid, State) - end. - --spec start_spec(spec(any(), any())) -> {ok, - gleam@erlang@process:subject(message())} | - {error, gleam@otp@actor:start_error()}. -start_spec(Spec) -> - gleam@otp@actor:start_spec( - {spec, fun() -> init(Spec) end, 60000, fun loop/2} - ). - --spec start(fun((children(nil)) -> children(any()))) -> {ok, - gleam@erlang@process:subject(message())} | - {error, gleam@otp@actor:start_error()}. -start(Init) -> - start_spec({spec, nil, 1, 5, Init}). - --spec application_stopped() -> application_stop(). -application_stopped() -> - gleam_otp_external:application_stopped(). - --spec to_erlang_start_result( - {ok, gleam@erlang@process:subject(any())} | - {error, gleam@otp@actor:start_error()} -) -> {ok, gleam@erlang@process:pid_()} | {error, gleam@dynamic:dynamic_()}. -to_erlang_start_result(Res) -> - gleam@otp@actor:to_erlang_start_result(Res). diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@system.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@system.erl deleted file mode 100644 index 622e5ea..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam@otp@system.erl +++ /dev/null @@ -1,43 +0,0 @@ --module(gleam@otp@system). --compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). - --export([debug_state/1, get_state/1, suspend/1, resume/1]). --export_type([mode/0, debug_option/0, debug_state/0, status_info/0, system_message/0, do_not_leak/0]). - --type mode() :: running | suspended. - --type debug_option() :: no_debug. - --type debug_state() :: any(). - --type status_info() :: {status_info, - gleam@erlang@atom:atom_(), - gleam@erlang@process:pid_(), - mode(), - debug_state(), - gleam@dynamic:dynamic_()}. - --type system_message() :: {resume, fun(() -> nil)} | - {suspend, fun(() -> nil)} | - {get_state, fun((gleam@dynamic:dynamic_()) -> nil)} | - {get_status, fun((status_info()) -> nil)}. - --type do_not_leak() :: any(). - --spec debug_state(list(debug_option())) -> debug_state(). -debug_state(A) -> - sys:debug_options(A). - --spec get_state(gleam@erlang@process:pid_()) -> gleam@dynamic:dynamic_(). -get_state(From) -> - sys:get_state(From). - --spec suspend(gleam@erlang@process:pid_()) -> nil. -suspend(Pid) -> - sys:suspend(Pid), - nil. - --spec resume(gleam@erlang@process:pid_()) -> nil. -resume(Pid) -> - sys:resume(Pid), - nil. diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@task.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@task.erl deleted file mode 100644 index e004284..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam@otp@task.erl +++ /dev/null @@ -1,111 +0,0 @@ --module(gleam@otp@task). --compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). - --export([async/1, try_await/2, await/2, try_await_forever/1, await_forever/1]). --export_type([task/1, await_error/0, message/1]). - --opaque task(FWJ) :: {task, - gleam@erlang@process:pid_(), - gleam@erlang@process:pid_(), - gleam@erlang@process:process_monitor(), - gleam@erlang@process:selector(message(FWJ))}. - --type await_error() :: timeout | {exit, gleam@dynamic:dynamic_()}. - --type message(FWK) :: {from_monitor, gleam@erlang@process:process_down()} | - {from_subject, FWK}. - --spec async(fun(() -> FWL)) -> task(FWL). -async(Work) -> - Owner = erlang:self(), - Subject = gleam@erlang@process:new_subject(), - Pid = gleam@erlang@process:start( - fun() -> gleam@erlang@process:send(Subject, Work()) end, - true - ), - Monitor = gleam@erlang@process:monitor_process(Pid), - Selector = begin - _pipe = gleam_erlang_ffi:new_selector(), - _pipe@1 = gleam@erlang@process:selecting_process_down( - _pipe, - Monitor, - fun(Field@0) -> {from_monitor, Field@0} end - ), - gleam@erlang@process:selecting( - _pipe@1, - Subject, - fun(Field@0) -> {from_subject, Field@0} end - ) - end, - {task, Owner, Pid, Monitor, Selector}. - --spec assert_owner(task(any())) -> nil. -assert_owner(Task) -> - Self = erlang:self(), - case erlang:element(2, Task) =:= Self of - true -> - nil; - - false -> - gleam@erlang@process:send_abnormal_exit( - Self, - <<"awaited on a task that does not belong to this process"/utf8>> - ) - end. - --spec try_await(task(FWP), integer()) -> {ok, FWP} | {error, await_error()}. -try_await(Task, Timeout) -> - assert_owner(Task), - case gleam_erlang_ffi:select(erlang:element(5, Task), Timeout) of - {ok, {from_subject, X}} -> - gleam_erlang_ffi:demonitor(erlang:element(4, Task)), - {ok, X}; - - {ok, {from_monitor, {process_down, _, Reason}}} -> - {error, {exit, Reason}}; - - {error, nil} -> - {error, timeout} - end. - --spec await(task(FWT), integer()) -> FWT. -await(Task, Timeout) -> - _assert_subject = try_await(Task, Timeout), - {ok, Value} = case _assert_subject of - {ok, _} -> _assert_subject; - _assert_fail -> - erlang:error(#{gleam_error => let_assert, - message => <<"Assertion pattern match failed"/utf8>>, - value => _assert_fail, - module => <<"gleam/otp/task"/utf8>>, - function => <<"await"/utf8>>, - line => 117}) - end, - Value. - --spec try_await_forever(task(FWV)) -> {ok, FWV} | {error, await_error()}. -try_await_forever(Task) -> - assert_owner(Task), - case gleam_erlang_ffi:select(erlang:element(5, Task)) of - {from_subject, X} -> - gleam_erlang_ffi:demonitor(erlang:element(4, Task)), - {ok, X}; - - {from_monitor, {process_down, _, Reason}} -> - {error, {exit, Reason}} - end. - --spec await_forever(task(FWZ)) -> FWZ. -await_forever(Task) -> - _assert_subject = try_await_forever(Task), - {ok, Value} = case _assert_subject of - {ok, _} -> _assert_subject; - _assert_fail -> - erlang:error(#{gleam_error => let_assert, - message => <<"Assertion pattern match failed"/utf8>>, - value => _assert_fail, - module => <<"gleam/otp/task"/utf8>>, - function => <<"await_forever"/utf8>>, - line => 149}) - end, - Value. diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp.app.src b/aoc2023/build/packages/gleam_otp/src/gleam_otp.app.src deleted file mode 100644 index 5c52295..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam_otp.app.src +++ /dev/null @@ -1,15 +0,0 @@ -{application, gleam_otp, [ - {vsn, "0.8.0"}, - {applications, [gleam_erlang, - gleam_stdlib, - gleeunit]}, - {description, "Fault tolerant multicore Gleam programs with OTP"}, - {modules, [gleam@otp@actor, - gleam@otp@intensity_tracker, - gleam@otp@port, - gleam@otp@supervisor, - gleam@otp@system, - gleam@otp@task, - gleam_otp]}, - {registered, []} -]}. diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp.erl b/aoc2023/build/packages/gleam_otp/src/gleam_otp.erl deleted file mode 100644 index 9381ad2..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam_otp.erl +++ /dev/null @@ -1,28 +0,0 @@ --module(gleam_otp). --compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). - --export([main/0]). - --spec spawn_task(integer()) -> gleam@otp@task:task(nil). -spawn_task(I) -> - gleam@otp@task:async(fun() -> case (I rem 500) =:= 0 of - true -> - gleam@io:println( - <<"Hello from "/utf8, (gleam@int:to_string(I))/binary>> - ); - - false -> - nil - end end). - --spec main() -> integer(). -main() -> - gleam@io:debug( - gleam_otp_test_external:get_message_queue_length(erlang:self()) - ), - _pipe = gleam@list:range(0, 1000000), - _pipe@1 = gleam@list:map(_pipe, fun spawn_task/1), - gleam@list:each(_pipe@1, fun gleam@otp@task:await_forever/1), - gleam@io:debug( - gleam_otp_test_external:get_message_queue_length(erlang:self()) - ). diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp.gleam b/aoc2023/build/packages/gleam_otp/src/gleam_otp.gleam deleted file mode 100644 index 69cdd5b..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam_otp.gleam +++ /dev/null @@ -1,27 +0,0 @@ -import gleam/io -import gleam/int -import gleam/list -import gleam/otp/task -import gleam/erlang/process.{type Pid} - -@external(erlang, "gleam_otp_test_external", "get_message_queue_length") -fn get_message_queue_length(pid pid: Pid) -> Int - -fn spawn_task(i) { - task.async(fn() { - case i % 500 == 0 { - True -> io.println("Hello from " <> int.to_string(i)) - False -> Nil - } - }) -} - -pub fn main() { - io.debug(get_message_queue_length(process.self())) - - list.range(0, 1_000_000) - |> list.map(spawn_task) - |> list.each(task.await_forever) - - io.debug(get_message_queue_length(process.self())) -} diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp_external.erl b/aoc2023/build/packages/gleam_otp/src/gleam_otp_external.erl deleted file mode 100644 index 8910a67..0000000 --- a/aoc2023/build/packages/gleam_otp/src/gleam_otp_external.erl +++ /dev/null @@ -1,43 +0,0 @@ --module(gleam_otp_external). - --export([application_stopped/0, convert_system_message/2]). - -% TODO: support other system messages -% {replace_state, StateFn} -% {change_code, Mod, Vsn, Extra} -% {terminate, Reason} -% {debug, {log, Flag}} -% {debug, {trace, Flag}} -% {debug, {log_to_file, FileName}} -% {debug, {statistics, Flag}} -% {debug, no_debug} -% {debug, {install, {Func, FuncState}}} -% {debug, {install, {FuncId, Func, FuncState}}} -% {debug, {remove, FuncOrId}} -% GetStatus(Subject(StatusInfo)) -convert_system_message({From, Ref}, Request) when is_pid(From) -> - Reply = fun(Msg) -> - erlang:send(From, {Ref, Msg}), - nil - end, - System = fun(Callback) -> - {system, {Request, Callback}} - end, - case Request of - get_status -> System(fun(Status) -> Reply(process_status(Status)) end); - get_state -> System(Reply); - suspend -> System(fun() -> Reply(ok) end); - resume -> System(fun() -> Reply(ok) end); - Other -> {unexpeceted, Other} - end. - -process_status({status_info, Module, Parent, Mode, DebugState, State}) -> - Data = [ - get(), Mode, Parent, DebugState, - [{header, "Status for Gleam process " ++ pid_to_list(self())}, - {data, [{'Status', Mode}, {'Parent', Parent}, {'State', State}]}] - ], - {status, self(), {module, Module}, Data}. - -application_stopped() -> - ok. |