Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: defining Akka.Analyzer rules #7039

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/articles/actors/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
uid: dependency-injection
title: Dependency injection
title: Dependency Injection Support in Akka.NET
---
# Dependency Injection

Expand Down
18 changes: 18 additions & 0 deletions docs/articles/debugging/akka-analyzers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
uid: akka-analyzers
title: Akka.Analyzers - Roslyn Analyzers and Code Fixes for Akka.NET
---

# Akka.Analyzers

As of Akka.NET v1.5.15 we now include [Akka.Analyzers](https://github.com/akkadotnet/akka.analyzers) as a package dependency for the core Akka library, which means any projects that reference anything depending on [`Akka`](https://www.nuget.org/packages/Akka) will automatically pull in all of Akka.Analyzer's rules and code fixes.

Akka.Analyzer is a [Roslyn Analysis and Code Fix](https://learn.microsoft.com/en-us/visualstudio/extensibility/getting-started-with-roslyn-analyzers) package, which means that it leverages the .NET compiler platform ("Roslyn") to detect Akka.NET-specific anti-patterns and problems during _compilation_, rather than at run-time.

## Supported Rules

| Id | Title | Severity | Category |
|--------|--------------------------------------------------------|----------|--------------|
| [AK1000](xref:AK1000) | Do not use `new` to create actors. | Error | Actor Design |
| [AK1001](xref:AK1001) | Should always close over `Sender` when using `PipeTo`. | Error | Actor Design |
| [AK2000](xref:AK2000) | Do not use `Ask` with `TimeSpan.Zero` for timeout. | Error | API Usage |
66 changes: 66 additions & 0 deletions docs/articles/debugging/rules/AK1000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
uid: AK1000
title: Akka.Analyzers Rule AK1000 - "Do not use `new` to create actors"
---

# AK1000 - Error

Do not use the `new` keyword to create Akka.NET actors.

## Cause

A class inheriting from [`Akka.Actor.ActorBase`](xref:Akka.Actor.ActorBase) or one of its descendants was instantiated directly via the `new` keyword, which is illegal. Actors can only be instantiated inside a [`Props.Create` method](xref:Akka.Actor.Props) or via an [`IIndirectActorProducer`](xref:Akka.Actor.IIndirectActorProducer) implementation (such as [`Akka.DependencyInjection`](xref:dependency-injection).)

An example:

```csharp
using Akka.Actor;

class MyActor : ActorBase {
protected override bool Receive(object message) {
return true;
}
}

class Test
{
void Method()
{
MyActor actorInstance = new MyActor(); // not supported by Akka.NET
}
}
```

## Resolution

The correct way to get a reference back to an actor is to wrap your actor's constructor call inside a `Props.Create` method and then pass that into either [`ActorSystem.ActorOf`](xref:Akka.Actor.ActorSystem) (when creating a top-level actor) or [`Context.ActorOf`](xref:Akka.Actor.IActorRefFactory#Akka_Actor_IActorRefFactory_ActorOf_Akka_Actor_Props_System_String_) (when creating a child actor.)

Here's an example below:

```csharp
using Akka.Actor;

class MyActor : ReceiveActor {
private readonly string _name;
private readonly int _myVar;

public MyActor(string name, int myVar){
_name = name;
_myVar = myVar;
ReceiveAny(_ => {
Sender.Tell(_name + _myVar);
});
}
}

class Test
{
void Method()
{
// obviously, don't create a new ActorSystem every time - this is just an example.
ActorSystem sys = ActorSystem.Create("MySys");
Akka.Actor.Props props = Akka.Actor.Props.Create(() => new MyActor("foo", 1));
IActorRef realActorInstance = sys.ActorOf(props);
}
}
```
60 changes: 60 additions & 0 deletions docs/articles/debugging/rules/AK1001.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
uid: AK1001
title: Akka.Analyzers Rule AK1001 - "Should always close over `Sender` when using `PipeTo`"
---

# AK1000 - Error

You should always close over [`Context.Sender`](xref:Akka.Actor.IActorContext#Akka_Actor_IActorContext_Sender) when using [`PipeTo`](xref:Akka.Actor.PipeToSupport#Akka_Actor_PipeToSupport_PipeTo_System_Threading_Tasks_Task_Akka_Actor_ICanTell_Akka_Actor_IActorRef_System_Func_System_Object__System_Func_System_Exception_System_Object__)

## Cause

When using `PipeTo`, you must always close over `Sender` to ensure that the actor's `Sender` property is captured at the time you're scheduling the `PipeTo`, as this value may change asynchronously.

This is a concurrent programming problem: `PipeTo` will be evaluated and executed at some point in the future because it's an asynchronous continuation, therefore the `Context.Sender` property, which is _mutable and changes each time the original actor processes a message_, may change.

An example:

```csharp
using Akka.Actor;
using System.Threading.Tasks;
using System;

public sealed class MyActor : UntypedActor{

protected override void OnReceive(object message){
async Task<int> LocalFunction(){
await Task.Delay(10);
return message.ToString().Length;
}

// potentially unsafe use of Context.Sender
LocalFunction().PipeTo(Sender);
}
}
```

## Resolution

To avoid this entire category of problem, we should close over the `Context.Sender` property in a local variable.

Here's an example below:

```csharp
using Akka.Actor;
using System.Threading.Tasks;
using System;

public sealed class MyActor : UntypedActor{

protected override void OnReceive(object message){
async Task<int> LocalFunction(){
await Task.Delay(10);
return message.ToString().Length;
}

var sender = Sender;
LocalFunction().PipeTo(sender);
}
}
```
47 changes: 47 additions & 0 deletions docs/articles/debugging/rules/AK2000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
uid: AK2000
title: Akka.Analyzers Rule AK2000 - "Do not use `Ask` with `TimeSpan.Zero` for timeout."
---

# AK2000 - Error

Do not use [`Ask<T>`](xref:Akka.Actor.Futures#Akka_Actor_Futures_Ask__1_Akka_Actor_ICanTell_System_Object_System_Nullable_System_TimeSpan__) or [`Ask`](xref:Akka.Actor.Futures#Akka_Actor_Futures_Ask_Akka_Actor_ICanTell_System_Object_System_Nullable_System_TimeSpan__) with `TimeSpan.Zero` for timeout.

## Cause

When using `Ask`, you must always specify a timeout value greater than `TimeSpan.Zero` otherwise the process might deadlock. See [https://github.com/akkadotnet/akka.net/issues/6131](https://github.com/akkadotnet/akka.net/issues/6131) for details.

> [!IMPORTANT]
> This rule is not exhaustive - Roslyn can't scan every possible variable value at compilation time, so it's still possible to pass in a `TimeSpan.Zero` value even with this rule present.

An example:

```csharp
using Akka.Actor;
using System.Threading.Tasks;
using System;

public static class MyActorCaller{
public static Task<string> Call(IActorRef actor){
return actor.Ask<string>(""hello"", TimeSpan.Zero);
}
}
```

## Resolution

The right way to fix this issue is to pass in a non-zero value or to use the `Ask<T>` overload that accepts a `CancellationToken`.

Here's an example below:

```csharp
using Akka.Actor;
using System.Threading.Tasks;
using System;

public static class MyActorCaller{
public static Task<string> Call(IActorRef actor){
return actor.Ask<string>(""hello"", TimeSpan.FromSeconds(1));
}
}
```
6 changes: 6 additions & 0 deletions docs/articles/debugging/rules/toc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- name: AK1000
href: AK1000.md
- name: AK1001
href: AK1001.md
- name: AK2000
href: AK2000.md
4 changes: 4 additions & 0 deletions docs/articles/debugging/toc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: Akka.NET Roslyn Analyzers
href: akka-analyzers.md
- name: Analysis Rules
href: rules/toc.yml
2 changes: 2 additions & 0 deletions docs/articles/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@
href: configuration/toc.yml
- name: Serialization
href: serialization/toc.yml
- name: Debugging Akka.NET
href: debugging/toc.yml
- name: Examples
href: examples.md