diff --git a/final/dry-run/Readme.md b/final/dry-run/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..b6fb049ae84bd4c9fd312d4dbde100b51bdfc420 --- /dev/null +++ b/final/dry-run/Readme.md @@ -0,0 +1,2 @@ +- Empty handouts and instructions: <https://gitlab.epfl.ch/lamp/cs206/-/tree/master/previous-exams/2021-final>. +- Solutions: <https://gitlab.epfl.ch/lamp/cs206/-/tree/master/previous-exams/2021-final-solutions> diff --git a/final/dry-run/concpar21final01/Readme.md b/final/dry-run/concpar21final01/Readme.md deleted file mode 100644 index 582f003c0744a6364b88921f0786ef74293ecc9c..0000000000000000000000000000000000000000 --- a/final/dry-run/concpar21final01/Readme.md +++ /dev/null @@ -1,55 +0,0 @@ -# Problem 1: Futures - -## Setup - -Use the following commands to make a fresh clone of your repository: - -``` -git clone -b concpar21final01 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar21final01 -``` - -If you have issues with the IDE, try [reimporting the -build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting), -if you still have problems, use `compile` in sbt instead. - -## Useful links - - * [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4) - * [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html) - * [The API documentation of the Play framework](https://www.playframework.com/documentation/2.8.x/api/scala/index.html) - -## Exercise - -In this exercise, your task is to implement a leaderboard webpage for a GitLab-based exam. This leaderboard is live in the sense that it is constructed on the fly by extracting grades directly from GitLab pipelines: it is always up to date. - -This exercise uses the Play framework, a popular web application framework. -Play is entirely asynchronous and uses futures for concurrency. - -In `src/main/scala/concpar21final01/MyComponents.scala`, we define a minimal Play application to display the leaderboard (you do not need to modify this file). - -You can start this application using `sbt run` and open the leaderboard in a web browser at [http://localhost:9000/](http://localhost:9000/). After having completed this exercise, you should see a populated leaderboard as shown in this screenshot: - - - -In this exercise, your task is to implement the `leaderboard()` method in `src/main/scala/concpar21final01/Problem1.scala` which asynchronously retrieves and sorts student grades. -Grades should be sorted such that maximum grades appear at the head of the list, like in the screenshot above. -Your implementation should use the following two methods to get the list of students and the grade of a particular student: - -```scala -/** Retrieve a student's grade using GitLab's API */ -def getGrade(sciper: Int): Future[Option[Grade]] - -/** Retrieve the list of enrolled students from IS-academia */ -def getScipers(): Future[List[Int]] -``` - -These methods have mock implementations that return made-up values after a short delay (simulating a network call). -Your implementation should be asynchronous (it is forbidden to use the `Await.result` method). -Furthermore, given the large number of students, calls to the GitLab API should be made in parallel such that the overall request is completed in about 1 second. - -*Hint:* this exercise can be solved without writing any recursive functions! You are allowed to use every function defined on [Future][1] and [List][2], as well as functions defined on their companion objects ([Future][3], [List][4]). - -[1]: https://www.scala-lang.org/api/2.13.4/scala/concurrent/Future.html -[2]: https://www.scala-lang.org/api/2.13.4/scala/collection/immutable/List.html -[3]: https://www.scala-lang.org/api/2.13.4/scala/concurrent/Future$.html -[4]: https://www.scala-lang.org/api/2.13.4/scala/collection/immutable/List$.html diff --git a/final/dry-run/concpar21final01/leaderboard.png b/final/dry-run/concpar21final01/leaderboard.png deleted file mode 100644 index 345da3a6dfb917967344ef7546d41755623336e3..0000000000000000000000000000000000000000 Binary files a/final/dry-run/concpar21final01/leaderboard.png and /dev/null differ diff --git a/final/dry-run/concpar21final02/Readme.md b/final/dry-run/concpar21final02/Readme.md deleted file mode 100644 index f5498d50b390aaa351e5f3e8bf6544252d89b717..0000000000000000000000000000000000000000 --- a/final/dry-run/concpar21final02/Readme.md +++ /dev/null @@ -1,85 +0,0 @@ -# Problem 2: Actors - -## Setup - -Use the following commands to make a fresh clone of your repository: - -``` -git clone -b concpar21final02 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar21final02 -``` - -If you have issues with the IDE, try [reimporting the -build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting), -if you still have problems, use `compile` in sbt instead. - -## Useful links - - * [A guide to the Scala parallel collections](https://docs.scala-lang.org/overviews/parallel-collections/overview.html) - * [The API documentation of the Scala parallel collections](https://www.javadoc.io/doc/org.scala-lang.modules/scala-parallel-collections_2.13/latest/scala/collection/index.html) - * [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4) - * [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html) - -## Exercise - -Your task is to implement a couple of actors in Discord like service. You will only focus on two actors in the system: (1) the `NotificationService` and (2) and a `DiscordChannel`. - -A user will be able to post messages on the channel and retrieve the messages from the channel. -A user also has the ability to (un)register from the notifications. -Before it can receive requests, each channel is initialized with a notification service. -When a message is posted to the channel, it tells its notification service to notify the users. - -The following diagram shows how the communication between actors works. Requests are within `()` and responses are within `[]`. - -```none - (Post) [Active] - (GetLastPosts) â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”┓ [AlreadyActive] â”â”â”â”â”â”â”â”â”â”┓ - ┌──────────────────────>┃ ┃──────────────────►┃ ┃ - │ ┃ DiscordChannel ┃ ┃ System ┃ - │ ┃ ┃◄──────────────────┨ ┃ - │ [Posts] â”—â”┯â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”┯┛ (Init) â”—â”â”â”â”â”â”â”â”â”â”› - â”â”â”â”·â”â”â”┓ [NotActive] │ │ - ┃ ┃◄────────────────────┘ │ - ┃ User ┃ │ - ┃ ┃◄────────────────┠│ - â”—â”â”┯â”â”â”â”› [Registered] │ │ (NotifyAll) - │ [Notification] │ │ - │ │ │ - │ â”â”â”â”â”â”·â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”┓ │ - └───────────────►┃ ┃ │ - (Register) ┃ NotificationService ┃◄─┘ - (UnRegister) ┃ ┃ - â”—â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”› -``` - -Your tasks in the exercise will be to: - -TASK 1: Complete the implementation of the `NotificationService` by implementing method `receive` to handle messages from `NotificationService.Protocol`: - -```scala -def receive: Receive = ??? -``` -TASK 2: Complete the implementation of the `DiscordChannel` by implementing methods `nonActive` and `active` to handle messages from `DiscordChannel.Protocol`: - -```scala -def nonActive: Receive = ??? -def active(notificationService: ActorRef): Receive = ??? -``` - -### NotificationService protocol - -* __Register__: Registers the user for notifications and responds with a `Registered(true)`. -* __UnRegister__: Un-registers the user for notifications and responds with a `Registered(false)`. -* __NotifyAll__: Sends a `Notification` to all registered users. - - -### DiscordService protocol - -The channel can be in one of two states: _non-active_ or _active_. - -* __Init__: When _non-active_, responds with `Active` and the state becomes _active_. Otherwise responds `AlreadyActive`. -* __Post__: When _active_, stores the message and sends `NotifyAll` to its `NotificationService`. Otherwise responds `NotActive`. -* __GetLastPosts__: When _active_, responds with `Posts` containing the latest messages (ordered from most to least recent). Otherwise responds `NotActive`. - -### Running the code - -Apart from the grading tests, we include a `@main def debug` method, where you can write your own tests to help you debug your implementation. This method is __not__ graded. It can be executed using `run` within SBT. diff --git a/final/dry-run/concpar21final03/Readme.md b/final/dry-run/concpar21final03/Readme.md deleted file mode 100644 index 0954e94b4dad3ea21afa3f5d27ab8c5dd209caa1..0000000000000000000000000000000000000000 --- a/final/dry-run/concpar21final03/Readme.md +++ /dev/null @@ -1,161 +0,0 @@ -# Problem 3: Concurrency - -## Setup - -Use the following commands to make a fresh clone of your repository: - -``` -git clone -b concpar21final03 git@gitlab.epfl.ch:lamp/student-repositories-s22/cs206-GASPAR.git concpar21final03 -``` - -If you have issues with the IDE, try [reimporting the -build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#troubleshooting), -if you still have problems, use `compile` in sbt instead. - -## Useful links - - * [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4) - * [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html) - -## Problem description -### Preliminary: File handling - -In this exercise, we will work with files using the following trait to handle -all file operations: -```scala -type FileName = String -trait FileSystem: - /** Create a new file named `file` with the passed `content`. */ - def createFile(file: File, content: String): Unit - /** If `file` exists, return its content, otherwise crash. */ - def readFile(file: File): String - /** If `file` exists, delete it, otherwise crash. */ - def deleteFile(file: File): Unit -``` -Note that to make testing easier, the actual implementation of `FileSystem` we will use -won't actually create files on disks, instead it will simply use a `Map` to -represent files and their content in memory, but that's an implementation detail -that won't affect how this exercise should be solved. - -### The update distribution problem - -You work at a game company on the popular online game EPFNiteâ„¢. Your job is -to distribute game updates to the players from the update server represented by -the following class: -```scala -class UpdateServer: - def fetchUpdate(): Option[String] - def newUpdate(newName: FileName, newContent: String): Unit -``` -The requirements of the update server are as follows: -- When a player starts his game, it connects to the update server which starts a - new thread and run `fetchUpdate()` which should return the content of the latest game update - if one is available. -- When a new version of the game is available, the developers call - `newUpdate` with the name of the update file and its content. -- Storage space is limited on the server, so when a new update is stored on the - server, old ones must be deleted. - -Based on these requirements, you come up with the following implementation: - -```scala -class UpdateServer(fs: FileSystem): - @volatile private var updateFile: Option[FileName] = None - - def fetchUpdate(): Option[String] = - updateFile.map(fs.readFile) - - def newUpdate(newName: FileName, newContent: String): Unit = - val oldFile = updateFile - fs.createFile(newName, newContent) - updateFile = Some(newName) - oldFile.foreach(fs.deleteFile) -``` -Unfortunately, it turns out that reading a file is not an atomic operation: if -you delete a file while another thread is reading it, your program crashes. -Theoretically, you could solve this using locks to make sure `deleteFile` -is never called at the same time as `readFile`, but this solution isn't good -enough for EPFNite: it's important that a player is never blocked from playing -the game because `fetchUpdate` is waiting for a lock to become available. - -Thankfully, there is one property of the problem which we can take advantage of: -a call to `fetchUpdate` which happens *after* `updateFile = Some(newName)` will -read the new file and not the old one, so all we need to do is to wait until all -calls to `fetchUpdate` which were started *before* we mutated `updateFile` have -finished before calling `deleteFile`. It turns out that there exists one -mechanism to do this efficiently: **RCU** (Read-copy-update) which you will implement in the next -section. - -## Implementation -### Part 1: Complete the `ThreadMap` implementation - -To implement RCU we will need a thread-safe way to associate a value to a -thread: this is the job of `ThreadMap` (defined in `ThreadMap.scala`) which we -implement with a `Map` whose keys are instances of `Thread` -(`Thread.currentThread` can be used to retrieve the instance for the current -thread). - -Instead of the usual `forall` method on collections, `ThreadMap` has a -`waitForall` method which will **block** until all entries of the map return -true for the predicate. For example given `val m: ThreadMap[Int]`, if one thread -runs: -```scala -m.setCurrentThread(1) -``` -and another thread runs: -```scala -m.waitForall(_ < 0) -``` -Then the second thread will be blocked, but if the first thread then runs: -```scala -m.setCurrentThread(-1) -``` -The second thread will be immediately unblocked. - -Your first task is to **implement all methods in `ThreadMap.scala` whose body is -currently `???`.** Once you're done, the "Part 1" test will pass. - -### Part 2: Complete the `RCU` implementation - -#### What is RCU ? - -The RCU API defines three methods: - -```scala -class RCU: - def startRead(): Unit - def stopRead(): Unit - def waitForOldReads(): Unit -``` - -It has the following contract: -- The first two methods are meant to be used by threads that read shared data: - they must must call `startRead` *before* reading shared data, then call - `stopRead` once they're done reading. -- `waitForOldReaders()` can be called from any thread: this is a blocking method - that only returns when all reads started *before* the call to - `waitForOldReaders()` are stopped (new reads may have started since then). - -#### Implementing RCU - -To implement RCU we need a way to differentiate reads started before a call to -`waitForOldReaders()` from those started after. To do so we will use *version -numbers*: the RCU version (starting at 0) is stored in `latestVersion`, and for -each *active* reader thread, we remember the value of `latestVersion` *at the time -startRead() was called* in `readersVersion`. Implementing `waitForOldReaders` is -then easy: -1. Increment the RCU version. -2. Wait until there's no active reader associated with a previous version. - -**Implement `waitForOldReaders` in `RCU.scala`**, for simplicity you can assume -that `waitForOldReaders` will never be called from multiple threads at once. -Once you're done, the "Part 2" test will pass. - -### Part 3: Using RCU in `UpdateServer` - -Finally, its time to put our RCU implementation to good use: **complete the -implementation of `fetchUpdate` and `newUpdate`** (defined in `UpdateServer.scala`) by adding calls to -`rcu.startRead()`, `rcu.stopRead()` and `rcu.waitForOldReaders()` where they -need to be to allow multiple calls to `fetchUpdate` and at most one call to -`newUpdate` to be run concurrently. Once you're done, the "Part 3" test will -pass.