I'm trying to convert one of my classes in Swift to an actor. The current init method of my class calls another instance method to do a bunch of initialization work. After converting, here's a simplified version of my actor:
actor MyClass {
private let name: String
init(name: String) {
self.name = name
self.initialize() // Error on this line
}
private func initialize() {
// Do some work
}
func login() {
self.initialize()
// Do some work
}
// Bunch of other methods
}
I get the following error when I try to compile:
Actor-isolated instance method 'initialize()' can not be referenced from a non-isolated context; this is an error in Swift 6
I found that I can replace self.initialize() with:
Task { await self.initialize() }
Is that the best way to do this? Could this cause any race conditions where an external caller can execute a method on my actor before the initialize() method has a chance to run? Seems cumbersome that you are not in the isolated context in the actor's init method. I wasn't able to find any Swift documentation that explained this.
I wasn't able to find any Swift documentation that explained this.
This is explained in the proposal SE-0327, in this section (emphasis mine):
An actor's executor serves as the arbiter for race-free access to the
actor's stored properties, analogous to a lock. A task can access an
actor's isolated state if it is running on the actor's executor. The
process of gaining access to an executor can only be done
asynchronously from a task, as blocking a thread to wait for access is
against the ethos of Swift Concurrency. This is why invoking a
non-async method of an actor instance, from outside of the actor's
isolation domain, requires an await to mark the possible suspension.
The process of gaining access to an actor's executor will be referred
to as "hopping" onto the executor throughout this proposal.
Non-async initializers and all deinitializers of an actor cannot hop
to an actor's executor, which would protect its state from concurrent
access by other tasks. Without performing a hop, a race between a new
task and the code appearing in an init can happen:
actor Clicker {
var count: Int
func click() { self.count += 1 }
init(bad: Void) {
self.count = 0
// no actor hop happens, because non-async init.
Task { await self.click() }
self.click() // 💥 this mutation races with the task!
print(self.count) // 💥 Can print 1 or 2!
}
}
To prevent the race above in init(bad:), Swift 5.5 imposed a restriction on what can be done with self in a non-async initializer. In particular, having self escape in a closure capture, or be passed (implicitly) in the method call to click, triggers a warning that such uses of self will be an error in Swift 6.
They then went on to give another example that should have been accepted by the compiler, and suggested to loosen those restriction. In either case though, it does not apply to your code. See this section if you want the details about why it still does not compile in newer versions of Swift.
So the solution is, as the first quote says, mark the init as async. This way the initialiser would be effectively run on the actor's executor. The caller would use await when initialising, to "hop" in.
Swift Lambda's can respond to requests by adopting LambdaHandler. To do so the following method must be implemented:
public func handle(context: Lambda.Context, event: APIGateway.V2.Request, callback: #escaping (Result<APIGateway.V2.Response, Error>) -> Void)
The idea is that — with successful requests — you'd return a successful response by executing the closure parameter passing a .success(APIGateway.V2.Response); and in that case it's clear what the body of the response would be — as well as the response code — as you specify them explicitly in the APIGateway.V2.Response initializer.
My question is: What is the body of the response when you pass a .failure(Error) to the given closure?
If a web service experiences an error that prevents it from returning a response in a controlled fashion that's one thing, but it's not uncommon for an application-specific error to occur that should be specified to the consumer of the service. But because there is no explicit way to specify what the body should be in the event of a unsuccessful request, it's ambiguous as to how that should be done.
Should the given Error's errorDescription property be the body of the response?
Should we be executing the closure with a value of .success in this case — despite not being successful — and passing an APIGateway.V2.Response object with an appropriate response code and the body we're intending?
My application is using Akka-Http to handle requests. I would like to somehow pass incoming extracted request context (fe. token) from routes down to other methods calls without explicitly passing them (because that would require modyfing a lot of code).
I tried using Dynamic Variable to handle this but it seems to not work as intended.
Example how I used and tested this:
object holding Dynamic Variable instance:
object TestDynamicContext {
val dynamicContext = new DynamicVariable[String]("")
}
routes wrapper for extracting and setting request context (token)
private def wrapper: Directive0 = {
Directive { routes => ctx =>
val newValue = UUID.randomUUID().toString
TestDynamicContext.dynamicContext.withValue(newValue) {
routes(())(ctx)
}
}
I expected to all calls of TestDynamicContext.dynamicContext.value for single request under my wrapper to return same value defined in said wrapper but thats not the case. I verified this by generating for each request separate UUID and passing it explicitly down the method calls - but for single request TestDynamicContext.dynamicContext.value sometimes returns different values.
I think its worth mentioning that some operations underneath use Futures and I though that this may be the issue but solution proposed in this thread did not solve this for me: https://stackoverflow.com/a/49256600/16511727.
If somebody has any suggestions how to handle this (not necessarily using Dynamic Variable) I would be very grateful.
Approach 1:
#PostMapping("/api/{id}")
String getSomeObj(int id){
//make another rest call with id and get CustomObj
// then do some logic and return something
//Here response time will be more as it has again another rest calls
}
Approach 2:
#PostMapping("/api/{id}")
String getSomeObj(#PathParam("id") int id, #RequestBody CustomObj obj){
//directly do logic with the provided obj and return something
//Here Response time would be less as we are directly getting the actual Object from Request Body
//BUT is this a good practise to pass an object in which we need only few details?
}
Q1) All I am asking is to whether to pass just id or Object? If id is passed, another Rest call has to be made unnecessarily. If Object is passed, we can avoid making another rest call, BUT the problem is: this custom object may contain some irrelavant details too.. So, is this correct?
Q2) If passed with id, response time will be more when comparing with just passing object.. So, I am not understanding which approach should follow..
A1) This is all up to you and there is no "one correct" way. I would say if it's a small object pass the object and respond fast. If its a big object pass the id. How do you define big and small objects? if object has hashmaps or lists in it that's a big object. Also you can ignore serialization of internals; check https://www.baeldung.com/jackson-ignore-properties-on-serialization
A2) Pass the id and enjoy your REST service. After all REST is very fast. Don't worry about the speed of calls. If your back end function is fast and if you put a "loading" gif to front end; users will wait for the response.
My server application uses Scalatra, with json4s, and Akka.
Most of the requests it receives are POSTs, and they return immediately to the client with a fixed response. The actual responses are sent asynchronously to a server socket at the client. To do this, I need to getRemoteAddr from the http request. I am trying with the following code:
case class MyJsonParams(foo:String, bar:Int)
class MyServices extends ScalatraServlet {
implicit val formats = DefaultFormats
post("/test") {
withJsonFuture[MyJsonParams]{ params =>
// code that calls request.getRemoteAddr goes here
// sometimes request is null and I get an exception
println(request)
}
}
def withJsonFuture[A](closure: A => Unit)(implicit mf: Manifest[A]) = {
contentType = "text/json"
val params:A = parse(request.body).extract[A]
future{
closure(params)
}
Ok("""{"result":"OK"}""")
}
}
The intention of the withJsonFuture function is to move some boilerplate out of my route processing.
This sometimes works (prints a non-null value for request) and sometimes request is null, which I find quite puzzling. I suspect that I must be "closing over" the request in my future. However, the error also happens with controlled test scenarios when there are no other requests going on. I would imagine request to be immutable (maybe I'm wrong?)
In an attempt to solve the issue, I have changed my code to the following:
case class MyJsonParams(foo:String, bar:Int)
class MyServices extends ScalatraServlet {
implicit val formats = DefaultFormats
post("/test") {
withJsonFuture[MyJsonParams]{ (addr, params) =>
println(addr)
}
}
def withJsonFuture[A](closure: (String, A) => Unit)(implicit mf: Manifest[A]) = {
contentType = "text/json"
val addr = request.getRemoteAddr()
val params:A = parse(request.body).extract[A]
future{
closure(addr, params)
}
Ok("""{"result":"OK"}""")
}
}
This seems to work. However, I really don't know if it is still includes any bad concurrency-related programming practice that could cause an error in the future ("future" meant in its most common sense = what lies ahead :).
Scalatra is not so well suited for asynchronous code. I recently stumbled on the very same problem as you.
The problem is that scalatra tries to make the code as declarative as possible by exposing a dsl that removes as much fuss as possible, and in particular does not require you to explicitly pass data around.
I'll try to explain.
In your example, the code inside post("/test") is an anonymous function. Notice that it does not take any parameter, not even the current request object.
Instead, scalatra will store the current request object inside a thread local value just before it calls your own handler, and you can then get it back through ScalatraServlet.request.
This is the classical Dynamic Scope pattern. It has the advantage that you can write many utility methods that access the current request and call them from your handlers, without explicitly passing the request.
Now, the problem comes when you use asynchronous code, as you do.
In your case, the code inside withJsonFuture executes on another thread than the original thread that the handler was initially called (it will execute on a thread from the ExecutionContext's thread pool).
Thus when accessing the thread local, you are accessing a totally distinct instance of the thread local variable.
Simply put, the classical Dynamic Scope pattern is no fit in an asynchronous context.
The solution here is to capture the request at the very start of your handler, and then exclusively reference that:
post("/test") {
val currentRequest = request
withJsonFuture[MyJsonParams]{ params =>
// code that calls request.getRemoteAddr goes here
// sometimes request is null and I get an exception
println(currentRequest)
}
}
Quite frankly, this is too easy to get wrong IMHO, so I would personally avoid using Scalatra altogether if you are in an synchronous context.
I don't know Scalatra, but it's fishy that you are accessing a value called request that you do not define yourself. My guess is that it is coming as part of extending ScalatraServlet. If that's the case, then it's probably mutable state that it being set (by Scalatra) at the start of the request and then nullified at the end. If that's happening, then your workaround is okay as would be assigning request to another val like val myRequest = request before the future block and then accessing it as myRequest inside of the future and closure.
I do not know scalatra but at first glance, the withJsonFuture function returns an OK but also creates a thread via the future { closure(addr, params) } call.
If that latter thread is run after the OK is processed, the response has been sent and the request is closed/GCed.
Why create a Future to run you closure ?
if withJsonFuture needs to return a Future (again, sorry, I do not know scalatra), you should wrap the whole body of that function in a Future.
Try to put with FutureSupport on your class declaration like this
class MyServices extends ScalatraServlet with FutureSupport {}