SupervisorStrategy strategy not respecting maxNrOfRetries - akka.net

I'm learning Akka.net and am stuck trying to get an actor to restart on failure.
With the following, I would have expected the "child has error" string to display 5 times (maxNrOfRetries), however I only see it once.
Can someone please point out what I'm missing? Is my code wrong, my understanding? Both?
Here is the LINQPad script http://share.linqpad.net/t8x5wl.linq
Thank you
async Task Main()
{
var system = ActorSystem.Create("MySystem");
var parent = system.ActorOf(Props.Create<ParentActor>(), "parent");
var child = await parent.Ask<IActorRef>(new GetChild());
child.Tell(new HelloChild());
child.Tell(new CauseChildToThrow());
child.Tell(new HelloChild());
child.Tell(new StopChild());
await parent.Ask<object>(new EndProcess());
"fin".Dump();
}
public class ParentActor : ReceiveActor
{
public ParentActor()
{
var child = Context.ActorOf(Props.Create<ChildActor>(), "child");
Receive<GetChild>(msg => Sender.Tell(child));
Receive<EndProcess>(msg => Sender.Tell(new {}));
}
protected override SupervisorStrategy SupervisorStrategy()
{
return new OneForOneStrategy(
maxNrOfRetries: 5,
withinTimeRange: Timeout.InfiniteTimeSpan,
localOnlyDecider: x =>
{
//return Directive.Resume; // skips the bad messsage?
return Directive.Restart; // retries with the bad message?
});
}
}
public class ChildActor : ReceiveActor
{
protected override void PreStart()
{
"child prestart".Dump();
}
protected override void PreRestart(Exception reason, object message)
{
"child pre restart".Dump(reason.Message + " " + message.ToString());
}
public ChildActor()
{
"child constructor".Dump();
Receive<HelloChild>(msg => "Child says hi".Dump());
Receive<StopChild>(msg =>
{
"child stopping gracefully".Dump();
Context.Stop(Self);
});
Receive<CauseChildToThrow>(_ =>
{
"child has error".Dump();
throw new MyException();
});
}
}
public class GetChild { }
public class HelloChild { }
public class StopChild { }
public class CauseChildToThrow { }
public class EndProcess { }
public class MyException : Exception{ }

I found this issue. The dying actor needed to resend the failing message back to itself.
protected override void PreRestart(Exception reason, object message)
{
"child pre restart".Dump(reason.Message + " " + message.ToString());
Self.Tell(message);
}
Also, i had to remove the call to
child.Tell(new StopChild());
Working LINQPad script here http://share.linqpad.net/hv6w4g.linq

Related

Spring R2dbc: Is there are way to get constant stream from postgresql database and process them?

I want to fetch records for newly created records in a table in postgresql as a live/continuous stream. Is it possible to use using spring r2dbc? If so what options do I have?
Thanks
You need to use pg_notify and start to listing on it. Any change that you want to see should be wrapped in simple trigger that will send message to pg_notify.
I have an example of this on my github, but long story short:
prepare function and trigger:
CREATE OR REPLACE FUNCTION notify_member_saved()
RETURNS TRIGGER
AS $$
BEGIN
PERFORM pg_notify('MEMBER_SAVED', row_to_json(NEW)::text);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER member_saved_trigger
AFTER INSERT OR UPDATE
ON members
FOR EACH ROW
EXECUTE PROCEDURE notify_member_saved();
In java code prepare listener
#Service
#RequiredArgsConstructor
#Slf4j
class NotificationService {
private final ConnectionFactory connectionFactory;
private final Set<NotificationTopic> watchedTopics = Collections.synchronizedSet(new HashSet<>());
#Qualifier("postgres-event-mapper")
private final ObjectMapper objectMapper;
private PostgresqlConnection connection;
#PreDestroy
private void preDestroy() {
this.getConnection().close().subscribe();
}
private PostgresqlConnection getConnection() {
if(connection == null) {
synchronized(NotificationService.class) {
if(connection == null) {
try {
connection = Mono.from(connectionFactory.create())
.cast(Wrapped.class)
.map(Wrapped::unwrap)
.cast(PostgresqlConnection.class)
.toFuture().get();
} catch(InterruptedException e) {
throw new RuntimeException(e);
} catch(ExecutionException e) {
throw new RuntimeException(e);
}
}
}
}
return this.connection;
}
public <T> Flux<T> listen(final NotificationTopic topic, final Class<T> clazz) {
if(!watchedTopics.contains(topic)) {
executeListenStatement(topic);
}
return getConnection().getNotifications()
.log("notifications")
.filter(notification -> topic.name().equals(notification.getName()) && notification.getParameter() != null)
.handle((notification, sink) -> {
final String json = notification.getParameter();
if(!StringUtils.isBlank(json)) {
try {
sink.next(objectMapper.readValue(json, clazz));
} catch(JsonProcessingException e) {
log.error(String.format("Problem deserializing an instance of [%s] " +
"with the following json: %s ", clazz.getSimpleName(), json), e);
Mono.error(new DeserializationException(topic, e));
}
}
});
}
private void executeListenStatement(final NotificationTopic topic) {
getConnection().createStatement(String.format("LISTEN \"%s\"", topic)).execute()
.doOnComplete(() -> watchedTopics.add(topic))
.subscribe();
}
public void unlisten(final NotificationTopic topic) {
if(watchedTopics.contains(topic)) {
executeUnlistenStatement(topic);
}
}
private void executeUnlistenStatement(final NotificationTopic topic) {
getConnection().createStatement(String.format("UNLISTEN \"%s\"", topic)).execute()
.doOnComplete(() -> watchedTopics.remove(topic))
.subscribe();
}
}
start listiong from controller
#GetMapping("/events")
public Flux<ServerSentEvent<Object>> listenToEvents() {
return Flux.merge(listenToDeletedItems(), listenToSavedItems())
.map(o -> ServerSentEvent.builder()
.retry(Duration.ofSeconds(4L))
.event(o.getClass().getName())
.data(o).build()
);
}
#GetMapping("/unevents")
public Mono<ResponseEntity<Void>> unlistenToEvents() {
unlistenToDeletedItems();
unlistenToSavedItems();
return Mono.just(
ResponseEntity
.status(HttpStatus.I_AM_A_TEAPOT)
.body(null)
);
}
private Flux<Member> listenToSavedItems() {
return this.notificationService.listen(MEMBER_SAVED, Member.class);
}
private void unlistenToSavedItems() {
this.notificationService.unlisten(MEMBER_SAVED);
}
but remember that if something broke then you lost pg_notify events for some time so it is for non-mission-citical solutions.

Upgrade Solution to use FluentValidation Ver 10 Exception Issue

Please I need your help to solve FluentValidation issue. I have an old desktop application which I wrote a few years ago. I used FluentValidation Ver 4 and Now I'm trying to upgrade this application to use .Net framework 4.8 and FluentValidation Ver 10, but unfortunately, I couldn't continue because of an exception that I still cannot fix.
I have this customer class:
class Customer : MyClassBase
{
string _CustomerName = string.Empty;
public string CustomerName
{
get { return _CustomerName; }
set
{
if (_CustomerName == value)
return;
_CustomerName = value;
}
}
class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(obj => obj.CustomerName).NotEmpty().WithMessage("{PropertyName} is Empty");
}
}
protected override IValidator GetValidator()
{
return new CustomerValidator();
}
}
This is my base class:
class MyClassBase
{
public MyClassBase()
{
_Validator = GetValidator();
Validate();
}
protected IValidator _Validator = null;
protected IEnumerable<ValidationFailure> _ValidationErrors = null;
protected virtual IValidator GetValidator()
{
return null;
}
public IEnumerable<ValidationFailure> ValidationErrors
{
get { return _ValidationErrors; }
set { }
}
public void Validate()
{
if (_Validator != null)
{
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context); **// <======= Exception is here in this line**
_ValidationErrors = results.Errors;
}
}
public virtual bool IsValid
{
get
{
if (_ValidationErrors != null && _ValidationErrors.Count() > 0)
return false;
else
return true;
}
}
}
When I run the application test I get the below exception:
System.InvalidOperationException HResult=0x80131509 Message=Cannot
validate instances of type 'CustomerValidator'. This validator can
only validate instances of type 'Customer'. Source=FluentValidation
StackTrace: at
FluentValidation.ValidationContext1.GetFromNonGenericContext(IValidationContext context) in C:\Projects\FluentValidation\src\FluentValidation\IValidationContext.cs:line 211 at FluentValidation.AbstractValidator1.FluentValidation.IValidator.Validate(IValidationContext
context)
Please, what is the issue here and How can I fix it?
Thank you
Your overall implementation isn't what I'd consider normal usage however the problem is that you're asking FV to validate the validator instance, rather than the customer instance:
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context);
It should start working if you change it to:
var context = new ValidationContext<object>(this);
var results = _Validator.Validate(context);
You're stuck with using the object argument for the validation context unless you introduce a generic argument to the base class, or create it using reflection.

Issue with reactive nested flatMap calls

I need to perform an operation on two Monos. The difficulty is that one depend on the result of the other.
Let me explain:
I have a Mono<User> (I get that from a ServerRequest; User is a POJO).
I need to be able to extract the user email from the above Mono and pass it to the UserRepository in order to check whether the email already exists in DB.
If the user already exists I will throw a 400 error; otherwise, I will save the user contained in the ServerRequest.
Here is what I have tried in my handler:
public Mono<ServerResponse> saveUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.flatMap(user -> userRepository
.findByEmail(user.getEmail())
.flatMap(foundUser -> {
if (foundUser != null) {
System.out.println("found:" + foundUser);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Email already exists");
} else {
System.out.println("creating" + user);
return status(CREATED).contentType(APPLICATION_JSON).body(userRepository.save(user), User.class);
}
}));
}
User:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
private Integer id;
#Size(min = 2)
private String firstName;
#Size(min = 2)
private String lastName;
#Email
private String email;
}
UserRepository:
public interface UserRepository extends ReactiveCrudRepository<User, Long> {
#Query("select id, first_name, last_name, email from user u where u.email = :email")
Mono<User> findByEmail(String email);
}
However, It seems there's an issue with my subscription: none of my System.out.println are called when the endpoint is called. Can someone please help?
edit 1: Here is the router calling the above handler method:
#Configuration
public class UserRouter {
#Bean
public RouterFunction<ServerResponse> route(UserHandler userHandler) {
return RouterFunctions.route()
.GET("/api/user", accept(APPLICATION_JSON), userHandler::getUsers)
.POST("/api/sign-up", accept(APPLICATION_JSON), userHandler::saveUser)
.build();
}
}
The issue here is that you expect a null when the user is not found, however, in reactive streams null is invalid. Instead, reactive streams have a dedicated empty state and dedicated operators to handle the empty case.
In your example you could do the following:
public Mono<ServerResponse> saveUser(ServerRequest serverRequest)
{
return serverRequest.bodyToMono(User.class)
.flatMap(this::createUserIfNotExists);
}
private Mono<ServerResponse> createUserIfNotExists(User user)
{
return userRepository.findByEmail(user.getEmail())
.hasElement()
.flatMap(foundUser ->
{
if (foundUser)
{
System.out.println("found user");
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
"Email already exists");
} else
{
System.out.println("creating user");
return status(CREATED).contentType(APPLICATION_JSON)
.body(userRepository.save(user), User.class);
}
});
}
or another alternative:
public Mono<ServerResponse> saveUser(ServerRequest serverRequest)
{
return serverRequest.bodyToMono(User.class)
.flatMap(this::createUserIfNotExists);
}
private Mono<ServerResponse> createUserIfNotExists(User user)
{
return userRepository.findByEmail(user.getEmail())
.flatMap(foundUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
)
.switchIfEmpty(
userRepository.save(user)
.flatMap(newUser -> status(CREATED).contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser)))
);
}

How to return object from retrofit api get call

I am trying to get list of objects from api call with retrofit but i just cant find the way to do so :(
This is the function i built:
private List<Business> businesses getBusinesses()
{
List<Business> businessesList = new ArrayList<>();
Call<List<Business>> call = jsonPlaceHolderApi.getBusinesses();
call.enqueue(new Callback<List<Business>>() {
#Override
public void onResponse(Call<List<Business>> call, Response<List<Business>> response) {
if(!response.isSuccessful())
{
textViewResult.setText("Code: " + response.code());
return;
}
List<Business> businesses = response.body();
for(Business business : businesses)
{
String content = "";
content += "ID: " + business.getId() + "\n";
content += "Name: " + business.getName() + "\n";
content += "On promotion: " + business.isOnPromotion() + "\n\n";
textViewResult.append(content);
}
businessesList = businesses;
}
#Override
public void onFailure(Call<List<Business>> call, Throwable t) {
call.cancel();
textViewResult.setText(t.getMessage());
}
});
}
I am trying to get the businesses response and return it.
can anyone help me?
Feeling frustrated :(
The way your executing the Retrofit call is asynchronous - using call.enqueue. there's nothing wrong with this approach. In fact it's perhaps the best option, since network calls can take a while and you don't want to block unnecessarily.
Unfortunately, this means you cannot return the result from the function. In most scenarios, if you did, the call would likely finish after the return making your return useless.
There are several ways to deal with this, the simplest one is to use callbacks. For example:
interface OnBusinessListReceivedCallback {
void onBusinessListReceived(List<Business> list);
}
private void businesses getBusinesses(OnBusinessListReceivedCallback callback){
Call<List<Business>> call = jsonPlaceHolderApi.getBusinesses();
call.enqueue(new Callback<List<Business>>() {
#Override
public void onResponse(Call<List<Business>> call, Response<List<Business>> response) {
if(!response.isSuccessful()){
textViewResult.setText("Code: " + response.code());
return;
}
callback.onBusinessListReceived(response.body());
}
#Override
public void onFailure(Call<List<Business>> call, Throwable t) {
call.cancel();
textViewResult.setText(t.getMessage());
}
});
}
You can then call it like so:
getBusinesses(new OnBusinessListReceivedCallback() {
public void onBusinessListReceived(List<Business> list){
// list holds your data
}
});

WebTestClient used multiple times returns empty body sometimes

not sure, why this could be an issue, but I can't stabilize my unit-tests.
Here some snippets from my testclass:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { "spring.main.web-application-type=reactive" })
#RunWith(SpringRunner.class)
#TestPropertySource(locations = "classpath:application-test.properties")
public class SolrControllerV1Test {
#Inject
ApplicationContext context;
#LocalServerPort
int port;
private WebTestClient client;
#TestConfiguration
static class TestConfig {
#Bean
public TestingAuthenticationProvider testAuthentiationManager() {
return new TestingAuthenticationProvider();
}
#Bean
public SecurityWebFilterChain securityConfig(ServerHttpSecurity http, ReactiveAuthenticationManager authenticationManager) {
AuthenticationWebFilter webFilter = new AuthenticationWebFilter(authenticationManager);
return http.addFilterAt(webFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.build();
}
}
#Before
public void setUp() {
this.client = WebTestClient.bindToApplicationContext(context).configureClient().responseTimeout(Duration.ofDays(1L)).baseUrl("http://localhost:" + port).build();
}
private void defaultCheck(ResponseSpec spec) {
spec.expectStatus().isOk().expectBody().jsonPath("$.response.numFound").hasJsonPath();
}
#Test
#WithMockUser(roles = { "ADMIN" })
public void simpleUsrSelect() throws Exception {
ResponseSpec spec = this.client.get().uri("/" + serviceVersion + "/usr/select?q=*:*&fq=*:*&fl=USRTYP,USRKEY,USRCID&rows=1&start=10&sort=last_update desc").exchange();
defaultCheck(spec);
}
#Test
#WithMockUser(roles = { "ADMIN" })
public void simpleCvdSelect() throws Exception {
ResponseSpec spec = this.client.get().uri("/" + serviceVersion + "/cvd/select?q=*:*&rows=10000").exchange();
defaultCheck(spec);
}
.
.
.
}
There are some more unit-tests there, some of which are long running (>1sec). If I have enough unit-tests in the class (~5-8), of which 1 or 2 are taking a bit longer, the unit-tests start to break. This looks like a thread safety issue, but I don't know, what I'm doing wrong. Any ideas?
EDIT
Here the Server Part that made trouble:
#PreAuthorize("hasAnyRole('ADMIN','TENANT')")
public Mono<ServerResponse> select(ServerRequest request) {
return request.principal().flatMap((principal) -> {
return client.get().uri(f -> {
URI u = f.path(request.pathVariable("collection")).path("/select/").queryParams(
queryModifier.modify(principal, request.pathVariable("collection"), request.queryParams())
.onErrorMap(NoSuchFieldException.class, t -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Collection not found"))
.block()).build();
return u;
})
.exchange()
.flatMap((ClientResponse mapper) -> {
return ServerResponse.status(mapper.statusCode())
.headers(c -> mapper.headers().asHttpHeaders().forEach((name, value) -> c.put(name, value)))
.body(mapper.bodyToFlux(DataBuffer.class), DataBuffer.class);
})
.doOnError(t -> handleAuthxErrors(t, principal, request.uri()));
});
}
If I add a publishOn(Schedulers.elastic) right after the .exchange() part, it seems to be working. Since this is trial&error, and I don't really understand why the publishOn fixes the problem, does anybody else know? I'm not even sure, whether using springs reactive Webclient is blocking in this case, or not...
Thanks, Henning