Skip to content

Commit

Permalink
Implement deletion strategy for one-child, leaf and two-children nodes
Browse files Browse the repository at this point in the history
If a delete message is received by the BinaryTree, it switches to a new
context, sets itself as a middleman so that it receives the responses
before the MessageHandler in order to switch back to the original
context and forwards the deletion message to the root node.

From there on, we're just searching for the correct node and if we
finally found it, we send a DeleteChild event to its parent, respond to
the middleman with an OperationFinished event stop the node.

The BinaryTree actor, in the meantime, stores all incoming operations
in a queue, switches back to the original context and works throught the
whole queue.

I decided to use the in-order predecessor, meaning the largest value of
the left subtree as an replacement for the to be deleted node in order
to maintain the ordering of the tree.

A new message MovePredecessor propagates to the node with the "highest"
key, sends DeleteChild to its own parent, replaces its own parent and
its subtree with the ones of the deleted node and sends a ReplaceChild
event to its new parent, in order to be at the place of the old node.
  • Loading branch information
chrisonntag committed Jan 30, 2023
1 parent 08194ea commit c3ccb48
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 41 deletions.
11 changes: 6 additions & 5 deletions clossyne-py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ False
```

## Commands
SET: Inserts a key-value pair into the Clossyne store. Returns True if the insert was successful, False otherwise.
GET: Retrieves the value associated with a given key. Returns False if the key does not exist.
DEL: Deletes a key-value pair from the Clossyne store. Returns True if the deletion was successful, False otherwise.
RNG: Retrieves a range of key-value pairs from the Clossyne store.
EXT: Closes the connection to the Clossyne server. Sent automatically when the with context ends.

- SET: Inserts a key-value pair into the Clossyne store. Returns True if the insert was successful, False otherwise.
- GET: Retrieves the value associated with a given key. Returns False if the key does not exist.
- DEL: Deletes a key-value pair from the Clossyne store. Returns True if the deletion was successful, False otherwise.
- RNG: Retrieves a range of key-value pairs from the Clossyne store.
- EXT: Closes the connection to the Clossyne server. Sent automatically when the with context ends.
10 changes: 6 additions & 4 deletions clossyne-py/clossyne/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ def disconnect(self):
self.sock = None

def handle_response(self, command, response):
if(command == "SET"):
if (command == "GET" or command == "RNG"):
# OperationResult event
return None if (response == "NS" or response == "NIL") else response
else:
# OperationFinished event
return response == "OK"
elif (command == "GET"):
return response

def send(self, command: str, *args):
command = command.upper()
Expand All @@ -48,7 +50,7 @@ def send(self, command: str, *args):

try:
# sendall guarantees that all the data is sent before returning
self.sock.sendall(str.encode(command + "\n"))
self.sock.sendall(str.encode(command))
response = self.sock.recv(1024).decode()

return self.handle_response(command[:3], response)
Expand Down
49 changes: 45 additions & 4 deletions clossyne-py/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,60 @@ def get_next_pair(self):
value = str({k:v for (k, v) in zip(self.reader.headers, row)})
return key, value

def perform(self):
with clossyne.Clossyne('localhost', 4297) as c:
def perform_delete_leaf(self):
with clossyne.Clossyne('0.0.0.0', 4297) as c:
key, value = self.get_next_pair()
if (c.set(key, value)):
print("Value has been saved")
saved_value = c.get(key)
print(saved_value)
print("GET ", key, ": ", saved_value)
print("Delete ", key)
print(c.delete(key))
print("GET ", key, ": ", c.get(key))

def perform_delete_child(self):
with clossyne.Clossyne('localhost', 4297) as c:
print(c.set("a", "val"))
print(c.set("b", "value"))
print(c.delete("a"))
print(c.get("a"))
print(c.get("b"))

def perform_delete_children(self):
with clossyne.Clossyne('localhost', 4297) as c:
print(c.set("d", "val"))
print(c.set("b", "value"))
print(c.set("a", "value"))
print(c.set("c", "value"))
print(c.set("f", "value"))
print(c.set("e", "value"))
print(c.set("g", "value"))
print(c.delete("d"))
print(c.get("d"))
print(c.get("b"))

with clossyne.Clossyne('localhost', 4297) as c:
print(c.get("d"))
print(c.get("b"))

def perform_dataset(self, num):
with clossyne.Clossyne('0.0.0.0', 4297) as c:
for i in range(0, num):
key, value = self.get_next_pair()
c.set(key, value)
print("Delete Standing: ", c.delete("Standing"))
print("Delete Mike: ", c.delete("Mike"))
print("Get Ogren: ", c.get("Ogren"))
print("Get Bergman: ", c.get("Bergman"))
print("Get Nadia: ", c.get("Nadia"))


def clean(self):
self.reader.close()


if __name__ == '__main__':
test_class = TestClass('test/data/names.csv')
test_class.perform()
test_class.perform_dataset(400)
test_class.clean()

2 changes: 1 addition & 1 deletion clossyne/.idea/modules/clossyne-build.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion clossyne/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ java -jar target/scala-2.12/clossyne.jar
Alternatively, you can also run the Docker image of Clossyne by using the following command:

```
docker run -e CLOSSYNE_HOST=localhost -e CLOSSYNE_PORT=4297 -p 4297:4297 clossyne:latest
docker run -e CLOSSYNE_HOST="0.0.0.0" -p 4297:4297 christophsonntag/clossyne:1.0
```

This will start the Clossyne service on the specified host and port, and make it accessible via the specified port on the Docker host.
2 changes: 1 addition & 1 deletion clossyne/build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name := "clossyne"

version := "0.1"
version := "1.0"

scalaVersion := "2.13.10"

Expand Down
12 changes: 8 additions & 4 deletions clossyne/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
<level>INFO</level>
</filter>
<encoder>
<pattern>[%date{ISO8601}] [%level] [%logger] [%marker] [%thread] - %msg MDC: {%mdc}%n</pattern>
<!-- MDC: {%mdc} -->
<pattern>[%date{ISO8601}] [%level] [%logger] %n%msg%n%n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>target/myapp-dev.log</file>
<file>target/clossyne-test.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>[%date{ISO8601}] [%level] [%logger] [%marker] [%thread] - %msg MDC: {%mdc}%n</pattern>
<pattern>[%date{ISO8601}] [%level] [%logger]:%n%msg%n%n</pattern>
</encoder>
</appender>

Expand Down
4 changes: 2 additions & 2 deletions clossyne/src/main/scala/handler/MessageHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.util.ByteString
import akka.io.{IO, Tcp}
import operations._
import operations.{OperationReply, OperationFinished, GetResult}
import operations.{OperationReply, OperationFinished, OperationResult}

import searchtree.BinaryTree

Expand Down Expand Up @@ -48,7 +48,7 @@ class MessageHandler(val client: ActorRef, val binaryTree: ActorRef) extends Act
case OperationFinished(succeeded: Boolean, _: Option[String]) =>
val response: String = if(succeeded) "OK" else "NS"
this.client ! Write(ByteString.fromString(response))
case GetResult(succeeded: Boolean, value: Option[String]) =>
case OperationResult(succeeded: Boolean, value: Option[String]) =>
val response: String = if (succeeded) value.getOrElse("NIL") else "NS"
this.client ! Write(ByteString.fromString(response))
}
Expand Down
6 changes: 6 additions & 0 deletions clossyne/src/main/scala/operations/DeleteForward.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.christophsonntag.clossyne
package operations

import akka.actor.ActorRef

case class DeleteForward(requester: ActorRef, middleware: ActorRef, key: String) extends OperationForward
10 changes: 10 additions & 0 deletions clossyne/src/main/scala/operations/OperationForward.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.christophsonntag.clossyne
package operations

import akka.actor.ActorRef

trait OperationForward {
def requester: ActorRef
def middleware: ActorRef
def key: String
}
10 changes: 10 additions & 0 deletions clossyne/src/main/scala/operations/OperationForwardReply.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.christophsonntag.clossyne
package operations

import akka.actor.ActorRef

sealed trait OperationForwardReply {
def succeeded: Boolean
def destination: ActorRef
}
case class OperationForwardFinished(succeeded: Boolean, destination: ActorRef) extends OperationForwardReply
2 changes: 1 addition & 1 deletion clossyne/src/main/scala/operations/OperationReply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ sealed trait OperationReply {
def value: Option[String]
}
case class OperationFinished(succeeded: Boolean, value: Option[String]) extends OperationReply
case class GetResult(succeeded: Boolean, value: Option[String]) extends OperationReply
case class OperationResult(succeeded: Boolean, value: Option[String]) extends OperationReply
35 changes: 29 additions & 6 deletions clossyne/src/main/scala/searchtree/BinaryTree.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
package com.christophsonntag.clossyne
package searchtree

import akka.actor.{Actor, ActorRef, Props}
import operations.{Operation, OperationReply}
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import operations.{Delete, DeleteForward, Operation, OperationFinished, OperationForwardFinished, OperationForwardReply}

import scala.collection.immutable.Queue


class BinaryTree extends Actor {

class BinaryTree extends Actor with ActorLogging {
// immutable root
val root: ActorRef = context.actorOf(Props(classOf[BinaryTreeNode], "root", "root", true), "clossyneRootNode")
val root: ActorRef = context.actorOf(BinaryTreeNode.props("root", "root", ActorRef.noSender), "clossyneRootNode")
var pendingOperations: Queue[Operation] = Queue.empty[Operation]

def receive: Receive = {
case op: Operation => root ! op
case opReply: OperationReply => context.parent ! opReply
case op: Operation =>
op match {
case Delete(requester, key) =>
log.debug("Delete received: Changing context now")
context.become(nodeDeletion)
root ! DeleteForward(requester, self, key)
case _ => root ! op
}
case opReply: OperationForwardFinished => opReply.destination ! OperationFinished(opReply.succeeded, None)
}

def nodeDeletion: Receive = {
case op: Operation =>
log.debug(s"Enqueuing operation ${op}")
pendingOperations.enqueue(op)
case opReply: OperationForwardFinished =>
log.debug(s"Delete operation finished. Change context and send enqueued operations.")
context.become(receive)

opReply.destination ! OperationFinished(opReply.succeeded, None)
pendingOperations.map(self ! _)
pendingOperations = Queue.empty
}
}
Loading

0 comments on commit c3ccb48

Please sign in to comment.