Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
0xBLCKLPTN authored Jun 24, 2023
1 parent 3182b0c commit baae7d6
Show file tree
Hide file tree
Showing 20 changed files with 610 additions and 0 deletions.
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Memore

Memory-Cache service for `Kingdom-System` project.


## Usage
### Manually start server

```sh
user@machine %: iex -S mix
```

![alt text](https://github.com/0xBLCKLPTN/Memore/blob/master/docs/screenshots/code.png)

### Connect to server and manipulate with him
```sh
user@machine %:telnet 127.0.0.1 4040
```
![alt text](https://github.com/0xBLCKLPTN/Memore/blob/master/docs/screenshots/telnet_output.png)

### Commands:
```sh
GET, PUT, DELETE, CREATE
```

### Example:
```sh
user@machine %: telnet 127.0.0.1 4040
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

CREATE shopping_list

PUT shopping_list milk 1
PUT shopping_list name 'Andrey'

GET shopping_list milk
GET shopping_list name

DELETE shopping_list milk
DELETE shopping_list name
```

# Tests
All checks have now been passed
```sh
Finished in 0.1 seconds (0.06s async, 0.06s sync)
8 doctests, 2 tests, 1 failure
```

### TODO:
- [ ] ci/cd
- [ ] docker image
- [x] gen_tcp server
- [x] write docmodules
- [x] write normal README.md
21 changes: 21 additions & 0 deletions apps/memore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Memore

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `memore` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:memore, "~> 0.1.0"}
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/memore>.

43 changes: 43 additions & 0 deletions apps/memore/lib/memore.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule Memore do
use Agent, restart: :temporary
use Application
@moduledoc """
Documentation for Memore.
"""

@impl true
def start(_type, _args) do
Superwizard.start_link(name: Superwizard)
end

@doc """
Starts a new memore instance.
"""
def start_link(_opts) do
Agent.start_link(fn -> %{} end)
end

@doc """
Get a value from the 'memore' by `key`
"""
def get(memore, key) do
Agent.get(memore, &Map.get(&1, key))
end

@doc """
Pus the `value` for the giving `key` in the `memore`.
"""
def put(memore, key, value) do
Agent.update(memore, &Map.put(&1, key, value))
end

@doc """
Deletes `key` from `bucket`.
Returns the current value of `key`, if `key` exists.
"""
def delete(memore, key) do
Agent.get_and_update(memore, fn dict -> Map.pop(dict, key) end)
end

end
79 changes: 79 additions & 0 deletions apps/memore/lib/registry.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule Registry do
use GenServer

## Client API

@doc """
Starts the registry
"""

def start_link(opts) do

# 1. pass the name to GenServer's init
server = Keyword.fetch!(opts, :name)
GenServer.start_link(__MODULE__, server, opts)
end

@doc """
Looks up the memore pid for `name` stored in `server`
Returns `{:ok, pid}` if the bucket exists, `:error` otherwise.
"""
def lookup(server, name) do

# 2. Lookup os now done directrly in ETS, without accessing the server.
case :ets.lookup(server, name) do
[{^name, pid}] -> {:ok, pid}
[] -> :error
end
end

@doc """
Ensures there is a memore associated with given `name` in `server`.
"""
def create(server, name) do
GenServer.call(server, {:create, name})
end

## Server callbacks

@impl true
def init(table) do
# 3. We have replaced the names map by the ETS table.
names = :ets.new(table, [:named_table, read_concurrency: true])
refs = %{}
{:ok, {names, refs}}
end

# 4. The previous handle_call callback for lookup was removed

@impl true
def handle_call({:create, name}, _from, {names, refs}) do

# 5. Read and write to the ETS table insead of the map
case lookup(names, name) do
{:ok, pid} ->
{:reply, pid, {names,refs}}
:error ->
{:ok, pid} = DynamicSupervisor.start_child(MemoreSupervisor, Memore)
ref = Process.monitor(pid)
refs = Map.put(refs, ref, name)
:ets.insert(names, {name, pid})
{:reply, pid, {names, refs}}
end
end

@impl true
def handle_info({:DOWN, ref, :process, _pid, _reason}, {names,refs}) do
# 6. Delete from the ETS table instead of the map
{name, refs} = Map.pop(refs, ref)
:ets.delete(names, name)
{:noreply, {names, refs}}
end

@impl true
def handle_info(_msg, state) do
{:noreply, state}
end

end
17 changes: 17 additions & 0 deletions apps/memore/lib/supervisor.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Superwizard do
use Supervisor

def start_link(opts) do
Supervisor.start_link(__MODULE__, :ok, opts)
end

@impl true
def init(:ok) do
children = [
{DynamicSupervisor, name: MemoreSupervisor, stategy: :one_for_one},
{Registry, name: Registry},
]

Supervisor.init(children, strategy: :one_for_all)
end
end
32 changes: 32 additions & 0 deletions apps/memore/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule Memore.MixProject do
use Mix.Project

def project do
[
app: :memore,
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Memore, []}
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
]

end
end
52 changes: 52 additions & 0 deletions apps/memore/test/memore_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
defmodule MemoreTest do
use ExUnit.Case, async: true
doctest Memore

setup context do
_ = start_supervised!({Registry, name: context.test})
%{registry: context.test}
end

test "spawn memores", %{registry: registry} do
assert Registry.lookup(registry, "shopping") == :error

Registry.create(registry, "shopping")
assert {:ok, memore} = Registry.lookup(registry, "shopping")

Memore.put(memore, "milk", 1)
assert Memore.get(memore, "milk") == 1
end

test "remove memores on exit", %{registry: registry} do
Registry.create(registry, "shopping")
{:ok, memore} = Registry.lookup(registry, "shopping")
Agent.stop(memore)

_ = Registry.create(registry, "bogus")
assert Registry.lookup(registry, "shopping") == :error
end

test "removes bucket on crash", %{registry: registry} do
Registry.create(registry, "shopping")
{:ok, memore} = Registry.lookup(registry, "shopping")

# Stop the memore with non-normal reason
Agent.stop(memore, :shutdown)

_ = Registry.create(registry, "bogus")
assert Registry.lookup(registry, "shopping") == :error
end

test "are temporary workers" do
assert Supervisor.child_spec(Memore, []).restart == :temporary
end

test "memore can crash at any time", %{registry: registry} do
Registry.create(registry, "shopping")
{:ok, memore} = Registry.lookup(registry, "shopping")

# Simulate a memore crash by explicitly and synchronously shuttibg it down
Agent.stop(memore, :shutdown)
catch_exit Memore.put(memore, "milk", 3)
end
end
1 change: 1 addition & 0 deletions apps/memore/test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ExUnit.start()
21 changes: 21 additions & 0 deletions apps/memore_server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# MemoreServer

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `memore_server` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:memore_server, "~> 0.1.0"}
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/memore_server>.

55 changes: 55 additions & 0 deletions apps/memore_server/lib/memore_server.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
defmodule MemoreServer do
require Logger

def accept(port) do
{:ok, socket} =
:gen_tcp.listen(port, [:binary, packet: :line, active: false, reuseaddr: true])
Logger.info("Accepting connections on port #{port}")
loop_acceptor(socket)
end

defp loop_acceptor(socket) do
{:ok, client} = :gen_tcp.accept(socket)
{:ok, pid} = Task.Supervisor.start_child(MemoreServer.TaskSupervisor, fn -> serve(client) end)
:ok = :gen_tcp.controlling_process(client, pid)
loop_acceptor(socket)
end

# socket |> read_line() |> write_line(socket) == write_line(read_line(socket), socket)
defp serve(socket) do
msg =
with {:ok, data} <- read_line(socket),
{:ok, command} <- MemoreServer.Command.parse(data),
do: MemoreServer.Command.run(command)

write_line(socket,msg)
serve(socket)
end

defp read_line(socket) do
:gen_tcp.recv(socket, 0)
end

defp write_line(socket, {:ok, text}) do
:gen_tcp.send(socket, text)
end

defp write_line(socket, {:error, :not_found}) do
# Known error; write to the client
:gen_tcp.send(socket, "NOT FOUND\r\n")
end

defp write_line(_socket, {:error, :closed}) do
# The connection was closed, exit politely
exit(:shutdown)
end

defp write_line(socket, {:error, error}) do
:gen_tcp.send(socket, "ERROR\r\n")
exit(error)
end

defp write_line(socket, {:error, :unknown_command}) do
:gen_tcp.send(socket, "UNKNOWN COMMAND\r\n")
end
end
Loading

0 comments on commit baae7d6

Please sign in to comment.