# Migrating to 2.4

> Tako 2.4 needs no code changes; review the core pinning default, the coarser HTTP/1 header deadline, and the extractor ENTRIES list.

Tako 2.4.0 makes the HTTP/1 request path cheaper and needs no code changes.
Per-thread workers no longer pin to cores by default, one timeout behaves
slightly differently, request extensions are dropped earlier, and custom
extractors can opt into the cheaper path. See the [release notes](https://github.com/rust-dd/tako/releases/tag/v2.4.0)
for the complete list.

## Per-thread workers no longer pin to cores

`PerThreadConfig::pin_to_core` now defaults to `false`. A pinned worker cannot
move off a core that another process keeps busy, and every process pins from
core 0, so two per-thread servers on one machine competed for the same cores.
To keep the 2.3 behaviour on a machine the server has to itself, set it back:

```rust
use tako::PerThreadConfig;

let config = PerThreadConfig {
  pin_to_core: true,
  ..PerThreadConfig::default()
};
```

## The header deadline is checked every half deadline

On the Tokio server's plain HTTP/1 listener (`spawn_http` and the `serve`
functions) and on per-thread workers, `header_read_timeout` no longer starts a
timer for every request. Each connection checks it every half deadline instead,
so a connection that stops sending request heads closes between 1 and 1.5 times
`header_read_timeout` after it went idle, rather than exactly at it. A
connection that is still running a handler or streaming a response is never
closed by it. TLS, Unix socket, vsock, and PROXY protocol listeners keep the
exact timer.

## Extractors list the entries they read

`FromRequest` and `FromRequestParts` gain an associated `ENTRIES` constant of the
new `tako::extractors::Entries` type. On a route without middleware or other
per-request hooks, the router attaches only the entries that the handler's
extractors list, such as the matched path or the connection info. The default,
`Entries::ALL`, keeps existing extractors working unchanged; see
[Writing an extractor](/docs/extractors#writing-an-extractor) to list fewer.

## Request extensions are dropped before the handler runs

A handler hands the request's header and extension maps back for reuse as soon
as its last extractor has run. A value that middleware put into the request
extensions is therefore dropped before the handler body runs, not after it
returns. Values that extractors read, such as a session or JWT claims, are not
affected, because the extractor keeps its own copy. A middleware that stores a
guard there, such as a semaphore permit, and needs it to live until the handler
finishes should hold the guard itself:

```rust
use std::sync::Arc;
use tokio::sync::Semaphore;

let limit = Arc::new(Semaphore::new(64));
router.middleware(move |req, next| {
  let limit = Arc::clone(&limit);
  async move {
    let _permit = limit.acquire_owned().await;
    next.run(req).await
  }
});
```

## Performance changes

These need no code changes:

* Routes without middleware or other per-request hooks take a shorter dispatch
  path.
* Request header and extension maps are reused on the thread that served them,
  and small handler futures are stored inline. A hello-world request on a
  per-thread worker's keep-alive connection makes no heap allocation, down from
  11 in 2.3.
* Plain HTTP/1 and per-thread connections check for shutdown with one atomic
  load and set no timer per request.
* The per-thread accept loop runs as its own task, so it is no longer polled
  each time a connection wakes.

Against 2.3.0 in the same [benchmark run](/docs/benchmarks), the default
server serves 20% more hello-world requests at 1,000 connections and 53% more
pipelined, and per-thread workers serve 9% more when the server is the
bottleneck and 30% more pipelined, each with its default settings.

Source: https://tako.rust-dd.com/docs/reference/migration-2-4