Leading items
Welcome to the LWN.net Weekly Edition for August 13, 2020
This edition contains the following feature content:
- End-to-end network programmability: "the network is the computer", 2020 version.
- Building a Flutter application (part 1): digging into Google's cross-platform user-interface library.
- 5.9 Merge window, part 1: the first set of changes merged for the 5.9 development kernel.
- Local locks in the kernel: a new locking mechanism from the realtime tree merged for 5.8.
- PHP struggles with attributes syntax: an ongoing debate on how attributes should be handled in PHP 8.0.
This week's edition also includes these inner pages:
- Brief items: Brief news items from throughout the community.
- Announcements: Newsletters, conferences, security updates, patches, and more.
Please enjoy this week's edition, and, as always, thank you for supporting LWN.net.
End-to-end network programmability
Nick McKeown kicked off the virtual Netdev 0x14 conference with a talk on extending the programmability of networking equipment well beyond where it is today. His vision is of an end-to-end system with programmable pieces at every level. Getting there will require collaboration between the developers of the networking stacks on endpoint operating systems as well as those of switches, routers, and other backbone equipment. The keynote was held on July 28, a little over two weeks before the seven days of talks, workshops, and tutorials for Netdev, which begins on August 13.
McKeown began by noting that he has used free operating systems throughout his 30-year career in networking, first BSD, then Linux. Those operating systems have shaped networking in various ways; they have also shaped how networking is taught to undergraduates at Stanford University, where he is a professor. The Linux infrastructure is "an amazing example of networking at its best that we show to our students and try to get them experience getting their hands dirty using it", he said.
He is a "huge believer in the open-source community for networking". In his group at Stanford, all of the code is released as open source. The "real revolution in networking" over the last ten years or more has been the rise of open source as a "trustworthy infrastructure for how we learn about and operate networks". Ten or 12 years ago, everyone was using closed-source, proprietary networking equipment, but today's largest data centers are all running on mostly open-source software, mainly on Linux-based equipment.
This change is pleasing to him—not simply for the sake of openness—but because it has allowed the owners and operators of this equipment to be able to program it. Those players can then introduce changes into their networks to improve their service in various ways. That kind of innovation can only be helpful to the networking world in the future.
A combination of express data path (XDP) and BPF provides the ability to do fast packet forwarding in the Linux kernel. In parallel, new forwarding pipelines, hardware accelerators, switches, and smart network-interface cards (NICs) are emerging, many of which are programmable using the P4 language. How can those two things be brought together so that the benefits can be gained end-to-end? Those two "camps" could be determined to be in opposition to each other, but he hopes that does not end up being the case. If the two do not end up working together, he said, it "will only confuse developers and users".
Some history
When he was a graduate student in the early 90s, the internet was still called NSFNet and routers were called "Cisco boxes". These routers were CPU-based and could "process a whopping ten-thousand packets per second". He and some other students decided to create a multi-port Fiber Distributed Data Interface (FDDI) router called the Bay Bridge [PDF]. It used complex programmable logic devices (CPLDs—a precursor to FPGAs) instead of CPUs to try to outdo the commercial routers.
The Bay Bridge was controlled from a Sun workstation over SBus. The CPLDs implemented a new microcoded language that described how packets should be processed. That made the Bay Bridge fast; it could process around 100,000 packets per second—ten times what the commercial products could do. The device sat on the Berkeley University FDDI ring for about five years while McKeown and his colleagues went off and did other things.
After that, they wanted to add some new features to the Bay Bridge; McKeown learned a valuable lesson from that experience. It was a "wake-up call on the rapid obsolescence, not only of microcode [...] but the obsolescence of brain cells because we couldn't remember how to program it". It ended up taking longer to add a fairly simple feature than it took to originally program the Bay Bridge. Creating a very long instruction word (VLIW) microcontroller seemed like a great idea at the time, but that experience has made him skeptical of microcode-programmable hardware accelerators.
When the idea of a network processor (or NPU) came about in the late 90s, he did not see that as the right path forward. Instead of stepping back and looking at the problem to be solved, NPU developers were simply creating a chip that contained an array of CPU cores. Network processing requires "extremely deep pipelines and very very fast I/O", neither of which is present on CPUs (or arrays of CPUs), he said.
He showed a graph that he has used in talks since around 2000, which compared the performance of network-processing chips and CPUs. Currently, the best chips can handle roughly 12.8Tbps, while CPUs are only a bit over 100Gbps, though you can argue a bit on the exact numbers, he said. When he first started looking at it, the difference was around 5x, but it is now around 100x. That led him to the conclusion that it was inevitable that something "based on a deep pipeline, high-speed I/O, and a fixed sequence of operations that corresponded to standard protocols" would have to be used for the highest performance. That would require the least amount of power, be most likely to fit on a single die, and, thus, would "provide the lowest overall cost".
For example, today you can get an application-specific integrated circuit (ASIC) switch that will handle 40 protocols at 10Tbps and use 400W. The CPU "equivalent" is 10Tbps for only four protocols and requires 25KW. CPUs are optimized for memory load and store operations, while the ASICs are optimized for I/O and pipelining. The upshot is that high-performance switches will be ASIC-based for the foreseeable future. He quoted "conventional wisdom", though he may have been the one who originally said it: "Programmable switches run 10-100x slower, consume more power, and cost more. Inherently."
The problem comes when a new protocol is needed. When the virtual extensible LAN (VXLAN) protocol needed to be added to the fixed-function switches, it took roughly four years for it to roll out in new hardware. Even though it takes a long time to get new features into the kernel, he said, four years is "pretty crazy". It makes him think that the development process we have for changing how packets are processed is wrong.
When the first programmable switches arrived, they were based on a variety of approaches: FPGAs, NPUs, or ASICs. None of them made it easy for the user to write code for their needs, so the device makers wrote the code. The device makers do not operate large networks, however, so they tended to simply implement existing standards—they are not going to be the ones that innovate, he said. All of that makes it hard to introduce new ideas, so everything tends to stagnate.
Domain-specific processing
He and others started looking at the domain-specific processors that have come about in various areas: GPUs for graphics and, it turned out, machine learning, digital signal processors (DSPs), tensor processing units (TPUs) for machine learning, and so on. Like with general-purpose computing on CPUs, all of the domain-specific processors had some higher-level languages that were compiled for the processor; those compilers would optimize the emitted code to take advantage of the instruction set and parallelism available.
In networking, there was no language where you could specify the behavior that would "lend itself to running at very high speed in an unrolled feed-forward path on a hardware accelerator", McKeown said. Around 2010 he and others started to think about a new domain-specific processor optimized for packet processing; it would specifically target allowing network operators to program it themselves. To that end, a new high-level language was needed that was hardware-independent and could be compiled to run at line-rate—all without sacrificing power, performance, or size.
Stanford started a project in collaboration with Texas Instruments, until TI got out of the large ASIC business, at which point the project started working with Barefoot Networks. The project developed the P4 language [PDF], which can target the protocol-independent switch architecture (PISA) that is provided by Barefoot Networks hardware.
McKeown described PISA at a high level. It consists of a programmable parser element that can pull apart packet headers based on a state-machine definition of the header layout. Each header then traverses a pipeline of match-and-action elements; one packet's headers are being processed at each stage of the pipeline, which provides one dimension of parallelism. Each of the match-and-action stages is identical and contains multiple elements that can do table-driven matches of various sorts (e.g. exact matches, associative matches) and take actions to modify the headers. There may be hundreds of match-and-action elements within each stage of the pipeline, which provides another dimension of parallelism.
There is a tendency to want to optimize the match-and-action stages based on how packets are usually processed today (e.g. layer 3 before layer 4), but they found that doing so gave too many constraints to the compiler. That is why all of those stages are identical; the number of stages is determined by the "degree of serial dependency in the programs". He showed four stages in the pipeline on his slide, but typically the pipelines are 16-25 stages deep. That provides room for programmers to add their own processing over and above the typical processing of today's protocols.
P4 is used to program the whole pipeline, including the parser, matches-and-actions stages, and the control flow. The matches and actions are made of tables that describe the types of matches to be done on various fields in the headers and the actions that should be taken when they occur; the control flow describes the sequence of tables that a packet will traverse on its way through the pipeline. Initially, the pipeline knows nothing about any protocol, so the information for any protocols (e.g. IPv4 and IPv6) must be specified.
The pipeline itself looks much like the fixed-function pipeline, he said, "so what's the big difference?" Unlike with a fixed-function device, the pipeline in a PISA device can be changed. He has been interested to see what kinds of changes network operators make on the PISA-based switch chips from Barefoot Networks, beyond just having the protocols that "we all know and love: IPv4, IPv6, etc.". Load-balancing changes are common, as is adding various types of telemetry to observe the behavior and performance of the switch. He is not sure that he would recommend it, but he has seen some use of non-standard IP address sizes (e.g. a 48-bit IPv4 address) on these programmable switches.
He showed a comparison of a fixed-function switch and one based on the Tofino P4-programmable chip from Barefoot Networks; both had 64 100Gbps ports and were pretty much identical other than the packet-processing chip used. The maximum forwarding rate was essentially the same, with the Tofino-based switch a bit higher. Power used per port was similar, but the Tofino was somewhat lower. Similarly, the latency was a bit less on the Tofino, but roughly equivalent. All of this shows that his conventional wisdom from earlier in the talk is now incorrect; programmable switches have the same performance, power, and cost characteristics as fixed-function devices. That means network operators will be likely to choose the programmable option for the additional flexibility it provides.
Where we are headed
Network owners and operators are "taking control of the software that controls their networks", McKeown said. It is "their lifeblood", so they need to ensure that it is reliable, secure, and can be extended in ways that allow them to differentiate themselves. To that end, they are also starting to take control of how their packets are being processed because of the availability of programmable switches and NICs. That is a transition that is starting to happen now as these devices all become more malleable.
He wondered what this means for how networks are going to be programmed in the future. He believes that we will think of the network as a programmable platform, rather than as a collection of separate elements; the behavior of the network will be described from the top (i.e. top-down). His hope is that the behavior will then be partitioned, compiled, and run across all of the elements in the network. There is still a lot of work to do to get there, however.
Furthermore, every data center will work differently, as they will be programmed locally to tailor them for the needs of the operator. It may be done to make them simpler, by removing protocols that are not needed, for example, or to add security measures specific to the needs of the organization that runs the data center.
A somewhat more controversial belief is that we will largely stop thinking in terms of protocols, he said; instead we will think in terms of software. The functions and protocols of today's internet will migrate into software. He suggested that many in the audience already thought that way, but that much of the network-development world is focused on interoperability, which is more of a concern when you are building things bottom-up and need to ensure that it all works well together. Today's large networks tend to be "somewhat homogeneous" that use similar equipment throughout. If you can express the desired behavior from the top "such that it is consistent across all of the devices, interoperability will matter, but much less than it used to" because the devices should work together by design, effectively.
All of this means that students of networking will need to learn about "programming a network top-down as a distributed computing platform". He and other educators need to figure out how to evolve their classes in that direction. It may mean that protocols are described in "quaint, historical terms"; things like routing and congestion control will instead be programs that are "partitioned across the system by a compiler".
Something that he thinks will be "absolutely huge" is the introduction of software-engineering techniques that will be routinely used in networking. If you have a specification of the behavior at the top level, then each API abstraction layer on the way down to packet processing can have its code checked for correctness. Techniques like unit testing, and even formal verification, can be applied to these programs to ensure that they are functioning correctly. We are a long way from being able to do that today, but all of the techniques either already exist or are within our reach as they are being researched and worked on today, he said.
Fine-grained per-packet telemetry will become more widespread, he believes. While in-band network telemetry [PDF] (INT) will be part of that, there will be other flavors and improvements over time. Once a device is programmable, the owner can determine what should be measured based on their needs; that will lead to a lot of innovation
The eventual goal, McKeown said, is that "we will have networks that are programmed by many and operated by few". They will be programmed by network operators and owners, but also by those who are studying and researching networking; hopefully those networks would be "operated by a lot fewer people than they are today". He noted that Stanford has around 35,000 people on its campus in normal times and that it takes around 200 people to keep the network running for them; that stands in stark contrast to the old telephone network that only took three people to keep it running. "We clearly haven't quite got it right yet in terms of making networks simple to operate and manage."
Programmable platform
The goal should be to be able to write code describing the network that is clear, will run at line-rate, and can be moved around to the component where it makes the most sense to run. He described the elements of the network pipeline, starting with user space, which uses the Data Plane Development Kit (DPDK) for networking in virtual machines (VMs), containers, or user-space programs. Then there is the kernel, which uses XDP and BPF. After that are the NICs and switches, which are increasingly being programmed using P4, though other languages or techniques may emerge.
The DPDK and XDP/BPF components already exist and work extremely well, he said; many of the developers of those pieces are in the audience. PISA and P4 are emerging on switches and he thinks those capabilities will also be moving into the NICs. So there is a potential collision between the two different ways of doing things; both are trying to efficiently handle packets in a familiar and easy-to-use way. He is not advocating leaving C and C++ behind, but, as XDP/BPF shows, there is a clear need for a "constrained way of programming" so that these programs can operate safely within the kernel.
As an example of the kind of program that might need to move to different elements in the pipeline, McKeown raised congestion control. It is somewhat "dangerous ground" that he is stepping into, because everyone has their "religious convictions about how congestion control should be done". He was not picking a side, but did want to show how the signals of congestion have moved around to the various parts of the pipeline for different algorithms.
The operators of clouds and large data centers have been experimenting with different techniques for determining when congestion is occurring. Originally, the signal for congestion was packet drops and duplicate ACKs, which is something that is mostly observed by the kernel. Later, round-trip time (RTT) was used as a signal, which required highly accurate timers so it was best done in the NICs. More recent research has looked at queue occupancy in the switches as a signal, which requires changes to those devices to gather the information along with changes to the NICs and kernel to handle a new header format to communicate the queue data. Adding VMs and containers into the mix, with a possibly different view of the congestion state from the underlying kernel or out on the NIC, makes things even more confusing. It makes sense that there would be a desire to be able to move things around to those various components as more is learned.
Routing has similar characteristics with regard to ideas changing over time. If there are ways that allow operators to move functionality around, they will do so, and the likely result is better techniques for networking. But the big question is how to get there. He said that he would be taking the "dangerous risk of putting a down a very tentative strawman". The overall problem deserves a lot of careful thought, because if it can be done right, it will have "dramatic consequences for the field of networking as a whole".
There is an enormous amount of user-space and kernel networking code that has already been written, which should be maintained going forward. But that general-purpose code will not directly run on a hardware-accelerated pipeline in a NIC or a switch; there needs to be some method of constraining those programs so that they can be run on those devices. Finding the right balance there "is not entirely obvious", McKeown said.
His idea is that the overall pipeline structure would be specified in P4 (or another language that allows specifying the serial dependencies of the processing). Using the P4 extern capability, much of the program code could still be written in C/C++, especially for things that will always run on a CPU. Other code would be written in P4 so that it could be moved to the hardware accelerators.
He gave an example of some functionality that is currently implemented in smart NICs for VM and container security in the cloud. When the cloud operators want to add new bare-metal systems, such as supercomputers, to their cloud, they cannot trust the NICs on those devices because they do not control the software that runs on them. They handle that by moving the security functionality into the switch. If they could just take the same code they are already running on the NIC and put it on the switch, it would make this process much easier.
Figuring all of this out is important, but he does not think that either the P4 or the Linux networking communities should try to figure this out on their own. There is expertise in both communities and, in the spirit of open source, they should come together to collaborate on solving these problems. He proposed that "netdev" (the kernel networking community centered around the netdev mailing list) and P4.org form a working group to specifically focus on finding open-source solutions to make all of this work end-to-end.
He wrapped up his talk by saying that, over the next decade, he believes that networks are going to become end-to-end programmable and that there is a need for collaboration to make it all work consistently. That will result in a lot of innovation in networking, and it will happen much faster than it would otherwise. Network operators will create the "dials that they need to observe the behavior" as well as "the control knobs that they need to change that behavior"; he suspects that most of the time they will use it to make their networks simpler, more reliable, and more secure. "Our job is to figure out how to make it possible for them to do so."
After his hour-long talk, McKeown spent another 30 minutes or so fielding a wide variety of questions from an obviously engaged audience. Even this long article did not cover everything; there is more that can be found in the video of the talk (and Q&A), which will be released sometime after Netdev 0x14 wraps up on August 21.
Building a Flutter application (part 1)
Flutter is a cross-platform open-source user-interface (UI) toolkit that is based on the Dart programming language. In early July, Canonical and Google worked together to bring Flutter to the Linux desktop. On August 5, Flutter version 1.20 was released, improving Flutter's performance, expanding its widget library, and more.
In a two-part series, we will be implementing a simple RSS reader for LWN. In part one, we will introduce the BSD-licensed Flutter and cover some Dart concepts that will be used by the application. Readers may also want to consult our introduction to Dart for more information on the language. Part two will build on the basic application to further flesh out the UI features.
According to its project page, Flutter's use-case is "for building
beautiful, natively compiled applications for mobile, web, and desktop from a
single codebase.
" Its GitHub repository indicates it has
over 660 contributors who make a little less than 100 commits per week.
Google claims that the project has had
two-million developers use the framework on a variety of platforms.
Getting started
Installing Flutter is not the most straightforward task, depending on the types of devices being targeted. According to the installation instructions for Linux, the easiest way to install Flutter is to use Ubuntu's snap packaging system, but instructions for manual installation are also provided. Getting Flutter itself installed is only the beginning of the process, however. A base Flutter SDK installation enables the building of Linux and browser-based (JavaScript) applications, but the Android SDK (specifically the Android Studio IDE) is also required to target Android devices. For iOS targets, a full installation of Apple's Xcode is needed.
Most interactions with Flutter during setup are done using the flutter command, which handles other tasks like package fetching and builds. One of the more useful sub-commands of flutter during installation is flutter doctor, which checks your environment and makes various recommendations on other software that may need to be installed to enable the framework's full capabilities. It is worth noting that the documentation on the flutter doctor command indicates that the flutter command sends anonymous usage statistics and basic crash reports to Google Analytics by default; instructions on how to disable sending this data are provided.
Writing some Flutter code
To introduce the language, we will walk through a simple application that pulls headlines from LWN via our RSS feed, and displays them. Readers can find the code for this article in a GitHub repository, available under an MIT license. Here is what the application will look like running in both iOS (left) and Android (right) emulators:
The project recommends using the Android Studio IDE when writing Flutter applications (it provides a number of niceties some developers may find useful), but any editor preferred by the developer can be used. For example, creating a new application project is done by using the flutter create command:
$ flutter create lwn_flutter_demo
This command will create a lwn_flutter_demo directory housing the project with, among other things, a lib/main.dart file which, by default, is considered the entry point into the application. Within that file you would find the main() function as with other languages such as C.
Before we get too far into code, we need to add a few dependencies to our project from the pub.dev repository, which is a large repository of open-source contributed libraries for Dart (and Flutter) projects not bundled as part of the core SDK. In this case, we will use two packages: http which provides HTTP request functionality, and dart_rss, which provides RSS feed parsing. That requires updating the pubspec.yaml file for the project to add the following in the dependencies section:
dependencies:
flutter:
sdk: flutter
http: ^0.12.0+2
dart_rss: ^1.0.1
Each dependency item provides a range of versions allowed for a given package. The documentation on pubspec.yaml version constraints is helpful to understating how to express the correct version(s) needed for the application. For the package version number itself, pub.dev follows semantic version specifications. Fetching the dependencies for the project based on the pubspec.yaml file is done using the flutter pub get command.
The lib/rss_service.dart file will implement the LWNRssService class responsible for making the HTTP request to LWN's RSS feed and gathering a list of items in that feed:
import 'package:dart_rss/domain/rss1_feed.dart';
import 'package:http/http.dart' as http;
class LWNRssService {
final _targetUrl = 'https://lwn.net/headlines/newrss';
Future<Rss1Feed> getFeed() =>
http.read(_targetUrl)
.then((xmlString) => Rss1Feed.parse(xmlString));
}
There are a lot of things happening in this small block of code worth discussing. We start by importing code from our two pub.dev packages: dart_rss and http. Next, we declare the LWNRssService class. This class defines a single private variable, _targetUrl, which is scoped as private because Dart automatically gives a private scope to variables that start with an underscore.
Finally, we define the getFeed() method, which takes advantage of the asynchronous features in Dart. It returns a Future of type Rss1Feed (Rss1Feed is provided by the dart_rss package). When getFeed() is called, it returns immediately with an instance of the _Future class that eventually will become the Rss1Feed object once the asynchronous task is completed. A great demonstration of how asynchronous code impacts execution flow can be found in the Dart documentation.
The implementation for the getFeed() function is abbreviated using shorthand arrow syntax to make it more compact. It starts by calling the http.read() method that returns a Future<String>. Upon completion of the HTTP request, the Future<String> then() method is called. This method takes a function to process the String retrieved from the HTTP request (the RSS feed). In our case, we pass that String into the Rss1Feed.parse() method provided by the dart_rss package, which parses the RSS feed and returns a Rss1Feed object. This object then becomes available from the Future instance originally returned from the getFeed() call.
In a Flutter application, the fundamental purpose of the main()
method is to create and execute the foundational Flutter widget that defines
the rest of the application — where widgets are some variety of
user-interface containers built up in a hierarchy. As Flutter's core
principles state: "Everything's a widget
". Widgets are
implemented as classes; commonly Flutter applications start with a MaterialApp
class. This class implements a widget for the basic outline of an application
using Google's Material Design UI
style. Alternatively, an application could start with a similar CupertinoApp
class, which provides a foundation that is similar to the UI style of iOS.
Either can be used, or a custom one could be made; we will use
MaterialApp. Here is the code for the lib/main.dart file of
our application:
import 'package:dart_rss/domain/rss1_feed.dart';
import 'package:flutter/material.dart';
import 'package:lwn_flutter_demo/rss_service.dart';
import 'homescreen.dart';
void main() async {
final Rss1Feed feed = await LWNRssService().getFeed();
runApp(DemoApp(feed));
}
class DemoApp extends StatelessWidget
{
final Rss1Feed feed;
DemoApp(this.feed);
@override
Widget build(BuildContext context)
{
return MaterialApp(
title: 'LWN Headlines',
home: LWNHeadlineScreen(feed)
);
}
}
We begin by importing several things, including a homescreen.dart file that we will discuss shortly. We define one function, the entry-point of our application main(), and one class, DemoApp, which extends the StatelessWidget class.
The main() function is declared async because it calls getFeed(), which returns a Future. In order for the variable feed to get an actual Rss1Feed value, though, the Future returned by getFeed() must be resolved by using an await statement, which will return the Rss1Feed object when the asynchronous function completes. In a more complex example, getFeed() could have been called and allowed to run asynchronously while other tasks were addressed — only waiting if necessary at the point where moving forward required the return value of the method.
Once the await operation has been satisfied and feed has been assigned, we call the Flutter runApp() function — passing it an instance of our DemoApp class. The DemoApp class takes a single parameter (an Rss1Feed object) in its constructor.
There are a few syntax oddities here that may confuse readers unfamiliar with Dart. Unlike PHP, object instances are created in Dart without using an explicit new keyword, as demonstrated in our example with runApp(DemoApp(feed)). Secondly, the constructor for DemoApp uses a shorthand syntax. Dart constructors allow for a simplified one-line syntax when the constructor's role is only to assign any passed values directly to the object's properties: DemoApp(this.feed). For reference, the shorthand version of the constructor used in our example would be equivalent to the following more verbose version:
DemoApp(Rss1Feed x) {
this.feed = x;
}
The last portion of the DemoApp class is the build() method, responsible for returning our root widget to the SDK. This method overrides the implementation of the StatelessWidget parent class (using the @override annotation) and returns a Widget class that will serve as the foundation of our application. The MaterialApp class offers many different options passed into the class constructor as named parameters; we will only use two of them. First is the title parameter, which defines the title of the application in the user interface. The second is the home parameter, which provides the default widget to show when the application loads. Flutter implements navigation for applications using routes, and in this case, the home parameter assigns a widget to the root (/) route.
For the home widget, we provide an instance of the LWNHeadlineScreen class and pass in the feed property from DemoApp(). The LWNHeadlineScreen class provides the real UI of our demo application and is implemented in the lib/homescreen.dart file referenced in main.dart. The implementation of this class is shown below:
import 'package:dart_rss/domain/rss1_feed.dart';
import 'package:flutter/material.dart';
class LWNHeadlineScreen extends StatelessWidget
{
final Rss1Feed feed;
LWNHeadlineScreen(this.feed);
@override
Widget build(BuildContext context)
{
return Scaffold(
appBar: AppBar(
title: Text(feed.title)
),
body: ListView.builder(
itemCount: feed.items.length,
itemBuilder: (BuildContext ctxt, int index) {
final item = feed.items[index];
return ListTile(
title: Text(item.title),
contentPadding: EdgeInsets.all(16.0),
);
}
)
);
}
}
Here, the build() method returns a somewhat complicated hierarchy of widget objects defining the user interface for our application. We start with a Scaffold class that accepts two named parameters appBar and body. Each of these parameters represents another widget in the UI. The appBar parameter, which defines the application toolbar, is implemented using the provided AppBar class. In our situation the toolbar only has a title, which is set to the value extracted from the RSS feed.
The body parameter defines the body of our application, and for that, we use a ListView class by calling its builder factory method. The ListView.builder() method is given two named parameters, which specify the number items available in the list (itemCount), and a function used to construct each element (itemBuilder). The function provided to build the list items will be called multiple times, passing in the index variable indicating which item in the list is currently being processed. The index is then used to extract an item from feed, which in turn is used to build a ListTile object for each item. These ListTile objects provide a title based on the feed item. Padding around the title text was added using the contentPadding parameter of ListTile; a 16-pixel padding was generated using the EdgeInsets class.
Executing Flutter applications
Now that we have an application to test with, it can be executed on various devices using the flutter run command. Flutter attempts to guess what the target device is, but it can be specified using the -d flag (flutter devices will provide a list of available targets). Beyond running in (emulated) iOS and Android environments, Flutter applications can run in web browsers and on Linux as well. Here we show it running on Ubuntu 20.04:
One of the nicer features of Flutter is the "hot reload" feature that allows applications to be reloaded with changes during development — without a full application rebuild. Developers can start a flutter run session to execute their application, make changes to the code, and see those changes reflected in the application without having to recompile. Saving a recompilation step speeds up the development cycle considerably.
Because Flutter support on Linux is currently alpha quality, the Flutter SDK must be specifically configured to support it. This configuration can be done by issuing a few commands to use the dev channel of Flutter's SDK, and enabling Linux desktop support with --enable-linux-desktop:
$ flutter channel dev
$ flutter upgrade
$ flutter config --enable-linux-desktop
Once activated, the Flutter application can also be run as a native Linux application using linux as the target device (flutter run -d linux).
When producing a JavaScript-based version of the application, things may not always work as expected. For example, because Chrome enforces cross-origin resource sharing (CORS) policies by default, our application would not run "out of the box" in Chrome; the RSS feed at LWN does not send the necessary CORS header required to allow JavaScript on Chrome to access it. To overcome this, we would need to run Chrome with CORS checking disabled. Likewise, other JavaScript restrictions (such as local filesystem access) may also pose difficulties that require consideration when building a Flutter application intended to function on a desktop as well as in a browser.
Wrapping up
In only around 60 lines of code, we were able to build a simple RSS headline display application that worked on Android, iOS, Linux, and a web browser. In part two of this series, we will build off of the code in this article to make the interface interactive and add other features. Readers interested in learning more can find plenty of tutorials and a complete API reference provided by the project.
5.9 Merge window, part 1
As of this writing, just over 3,900 non-merge changesets have been pulled into the mainline repository for the 5.9 kernel development cycle. While this merge window has just begun, there is already a significant set of new features to point out.
Architecture-specific
- Support for the Unicore architecture, which survived a purge of unused architectures in 2018, has now been removed for real.
- X86 kernels can now be built using Zstandard compression.
- The x86 kernel currently provides a special device that allows access to the CPU's model-specific registers. As described in this commit, though, providing that kind of access is asking for all kinds of trouble. As of 5.9, the kernel can filter MSR writes to an approved subset of registers, or block them entirely (which is the intended final goal).
- Support for the x86 FSGSBASE instructions has finally been merged, bringing a long story to a close. This work allows safe and efficient access to the FS and GS segment base registers from user space.
Core kernel
- The io_uring subsystem now has full support for asynchronous buffered read operations without the need to fall back to using kernel threads. Write support will be forthcoming in a future development cycle.
- The deadline scheduler has gained capacity awareness, meaning that it now makes correct decisions on asymmetric systems. See this documentation patch for details.
- There is a new sysctl knob named sched_uclamp_util_min_rt_default. It controls the default amount of CPU-frequency boosting that is applied when a realtime task runs, with the idea of limiting power usage on mobile systems. This documentation patch has (a bit) more information.
- The kernel's energy model, which until now only described the energy behavior of CPUs, has been extended to cover peripheral devices as well. See this documentation patch for (some) more information.
- The new close_range() system call allows a process to efficiently close a whole range of open file descriptors.
Filesystems and block I/O
- The new Btrfs rescue= mount option is meant to be the future home of all rescue-oriented options; for now it just contains aliases of existing options. The alloc_start and subvolrootid options have been removed.
- Btrfs has also seen some performance improvements, especially around fsync() operations.
- Inline encryption support was added in 5.8; in 5.9 that support will be provided in the ext4 and F2FS filesystems via the new inlinecrypt mount option. This allows these filesystems to make use of encryption support built into block-device controllers.
- The new debugfs= command-line option controls the availability of the debugfs filesystem. Setting it to on enables debugfs normally, while off disables it as if it were configured out of the kernel entirely. The no-mount option leaves debugfs enabled internally, but does not allow it to be mounted.
Hardware support
- Crypto: Silex Insight BA431 random number generators, TI K3 SA2UL crypto accelerators, and Ingenic random number generators.
- Miscellaneous: NVIDIA Tegra210 external memory controllers, Renesas RPC-IF SPI controllers, Corsair Commander Pro controllers, and Microchip Sparx5 SoC temperature sensors.
- Regulator: ChromeOS embedded-controller regulators, Qualcomm USB Vbus regulators, Qualcomm LAB/IBB regulators, Silergy SY8827N regulators, Fairchild FAN53880 regulators, and NXP PCA9450A/PCA9450B/PCA9450C regulators.
- Systems-on-chip: Intel Movidius (A.K.A. "Keem Bay"), Microchip Sparx5, and MStar/Sigmastar Armv7.
- USB: Xilinx ZynqMP PHYs, SAMSUNG SoC series UFS PHYs, Qualcomm IPQ806x DWC3 USB PHYs, and Ingenic X-series PHYs.
Security-related
- Kernels built with Clang can be configured to automatically initialize all on-stack variables to zero. As described in this commit, how Clang will support this feature in the future is unclear, so changes to this option are likely to be needed in response to future Clang releases.
- The seccomp subsystem, when used with user-space notification, now allows the supervisor process to inject file descriptors into the watched process. This enables full emulation of system calls that create new file descriptors.
- The new CAP_CHECKPOINT_RESTORE capability provides access to a number of features needed to checkpoint and restore processes without (other) privileges. This capability was covered here (as CAP_RESTORE) in June. See this merge commit for a description of the actions allowed by CAP_CHECKPOINT_RESTORE.
Internal kernel changes
- The smp_read_barrier_depends() barrier, which was only needed on the Alpha architecture, has been removed in favor of some smarter logic in READ_ONCE().
- GCC 11 provides all of the features needed to support the KCSAN data-race detector, so KCSAN can now be used with GCC-built kernels.
- The uninitialized_var() macro has been removed. Its purpose was to silence compiler warnings about variables that might be used without being initialized; in practice it has often ended up hiding bugs.
- A great deal of cleanup work in the kernel's entry and exit code in 5.8 has been topped off in 5.9 with a new set of generic entry and exit functions. There is necessarily a lot of architecture-specific work to be done on entry into the kernel, but the overall set of tasks — and the ordering relationships between them — are essentially the same. The new code, once adopted by the various architectures, should help to eliminate bugs in this area and prevent their reintroduction. See the commits for the entry, exit, and interrupt handlers for a better idea of how this all works.
The 5.9 merge window can be expected to remain open through August 16. There are a number of significant repositories yet to be pulled, so chances are that the second-half summary, to be posted on LWN shortly after that date, will include many more changes. Stay tuned.
Local locks in the kernel
The Linux kernel has never lacked for synchronization primitives and locking mechanisms, so one might justifiably wonder why there might be a need to add another one. The addition of local locks to 5.8 provides an answer to that question. These locks, which have their origin in the realtime (PREEMPT_RT) tree, were created to solve some realtime-specific problems, but they also bring some much-needed structure to a common locking pattern used in non-realtime kernels as well.
Lock types in Linux
The Linux kernel offers developers a number of lock types to choose from. They could be roughly divided, until recently, into two categories: spinning and sleeping locks.
A kernel function attempting to acquire a spinning lock that is owned by another thread will spin (loop actively) until the other thread, which must be running on a different CPU, releases the lock. This type of lock is fast, but it may waste CPU cycles if the wait lasts for a long time. Spinning locks are thus used around short sections of code. Longer code sections protected by spinning locks will increase the overall system latency; code that needs to respond to an event quickly may be blocked on a lock. The category of spinning locks contains spinlocks and read-write locks.
The situation is different with sleeping locks; a thread taking such a lock will, as the name suggests, relinquish the CPU if it cannot obtain the lock. This type of lock works for longer sections of critical code, but takes a longer time to obtain. Also, sleeping locks cannot be taken when a thread is running in atomic context; that happens, for example, when interrupts are disabled, the code holds a spinlock, or it holds an atomic kmap (atomic kernel mapping). In non-PREEMPT_RT kernels, sleeping locks include all types of mutexes and semaphores. In practice, even sleeping locks do spinning in some cases if there is a possibility to obtain the lock rapidly. For example, mutexes may spin if the mutex owner is running (and thus should release the mutex shortly). This is called "opportunistic spinning"; interested readers can look into the details in the kernel documentation.
The implementation of many lock types changes in the PREEMPT_RT kernels; in particular, most spinning locks becoming sleeping locks.
Disabling interrupts and preemption as locking
While not called "locking", another way of serializing access to certain types of data exists in practice, usually used in low-level code; it works by disabling interrupts, preemption, or both. These actions only apply to the running CPU.
The Linux kernel is preemptive, meaning that a task can be stopped at (almost) any moment to give the CPU to a higher-priority task. Tasks may be also moved to a different CPU at almost any time. Some code sections, usually those dealing with per-CPU data, need to ensure that they run continuously on the same CPU without interference from other tasks. This code may not need a global lock; since it only needs to modify per-CPU data, there should be no possibility of concurrent access from elsewhere. Such code can simply disable preemption with preempt_disable(), restoring it with preempt_enable(). If the goal is to use per-CPU data, additional helper functions exist; get_cpu() disables preemption and returns the current CPU ID, and put_cpu() enables preemption.
Interrupts may be delivered to the CPU while a task is executing; that too may cause unexpected concurrent access to per-CPU data. To prevent this problem, the developer can disable interrupt delivery with local_irq_disable() and then enable it with local_irq_enable(). If the code is running in a context where the interrupts might be already disabled, they should use local_irq_save() and local_irq_restore(); this variant saves and restores the previous status in addition to disabling or enabling the interrupts. It is worth noting that disabling interrupts also disables preemption. While interrupts are disabled, the code is running in atomic context and the developers need to be careful to avoid, among other things, any operations that may sleep or call into the scheduler.
These conventions for disabling preemption and interrupts are somewhat problematic. When a developer creates a lock, they (should) define exactly what is protected by that lock. Development tools like the lockdep checker can then help to ensure that the locking rules are followed in all cases. There is no lock when preemption disabling is used, though, and often no clear idea of what is being protected, making it easy for bugs to slip in.
The problems become more acute in the realtime setting. The key goal in a realtime kernel is that nothing must prevent the highest-priority task from running immediately. Disabling preemption can prevent the kernel from making the CPU available to a process that needs it, and disabling interrupts can delay the arrival of events that must be responded to as quickly as possible. The realtime developers have duly worked for many years to prevent disabling either of those things whenever an alternative exists.
Introducing local locks
The solution to these problems is to create a new type of explicit lock, called a "local lock". On non-realtime systems, the acquisition of a local lock simply maps to disabling preemption (and possibly interrupts). On realtime systems, instead, local locks are actually sleeping spinlocks; they do not disable either preemption or interrupts. They are sufficient to serialize access to the resource being protected without increasing latencies in the system as a whole.The local-lock operations are defined as macros and they use the local_lock_t type which, on non-realtime systems, only contains useful data when lock debugging is enabled. The developer can initialize a lock using local_lock_init(), then take the lock using local_lock() and release it using local_unlock(). To also disable interrupts, the developer can use local_lock_irq() and local_unlock_irq(). Finally, another set of functions allows disabling interrupts while remembering their previous state: local_lock_irqsave() and local_unlock_irqrestore().
Local lock operations map into the preemption and interrupt disabling in the following way:
local_lock(&llock) preempt_disable()
local_unlock(&llock) preempt_enable()
local_lock_irq(&llock) local_irq_disable()
local_unlock_irq(&llock) local_irq_enable()
local_lock_save(&llock) local_irq_save()
local_unlock_restore(&llock) local_irq_restore()
The advantages of local locks for non-PREEMPT_RT configurations is that they clarify what is actually being protected and they allow for lock debugging, including static analysis and runtime debugging with lockdep. This was not possible with direct calls to disable preemption and interrupts. Having a clear scope allows better documentation of locking in the code, with different local locks used in different parts of the code, instead of one set of context-less functions. In the realtime world, additionally, code holding a local lock can still be preempted if need be, preventing it from blocking a higher-priority task.
With the addition of local locks, the lock nesting rules change somewhat. Spinning locks can nest (when all other locking rules are met) into all other types of locks (spinning, local, or sleeping). Local locks can nest into sleeping locks.
Summary
Local locks have been merged for the 5.8 kernel, which means they are available now. This is a welcome addition, as it makes per-CPU locking have the same semantics as the other locks. This should avoid some errors, and allow lockdep in contexts where it was not possible to use before. In addition, it will make the merging of the remaining PREEMPT_RT patches that much easier.
PHP struggles with attributes syntax
PHP 8.0 is on the horizon, and the project has imposed a feature freeze for the release. There's one exception to the feature freeze, though: the new attributes syntax. An attribute is syntactical metadata for PHP code, identical to what is called an "annotation" in other languages. Even though attributes have been voted on multiple times by the community, major contributor and creator of XDebug Derick Rethans threw a wrench into the works days before the feature freeze by challenging the current syntax. The ensuing discussion lead to the fourth attributes proposal for the year, with a special feature freeze exception being made by release manager Sara Golemon. This exception gives Rethans one more opportunity to convince the community to change how attributes work up to the Beta 3 release, scheduled for September 3.
What are PHP attributes?
As said, attributes are metadata for code, and provide a way to attach metadata to classes, functions, object properties, and so on to add information that isn't necessarily relevant to the execution of that code (such as the name of the author responsible for writing a particular method). While they can be used strictly for informational purposes, attributes can also play an important role in the code itself — such as telling the PHP engine if a particular method should be compiled by the just-in-time compiler also coming in PHP 8.0. Currently, the idea of attributes in PHP takes the form of a pseudo-language implemented as multi-line comments known as DocBlocks. In PHP 8.0, the hope is to replace the need for these DocBlocks with a native attribute syntax that can be parsed by the PHP engine. These attributes would then be available to the PHP engine itself, as well as being made available to developers through PHP's reflection capabilities.
The idea of attributes in PHP is not a new concept; contributor Dmitry Stogov proposed it back in 2016 for PHP 7.1. After extensive discussion, this original proposal was not accepted. The concept of attributes was recently brought back to life for PHP 8.0 by Benjamin Eberlei with a new proposal based on Stogov's work. Eberlei went through considerable effort, according to his post, to update things based on the feedback from Stogov's failed attempt, writing at the time:
I want to resurrect Dmitrys Attributes RFC that was rejected for 7.1 in 2016 with a few changes, incorporating feedback from the mailing list back then and from talking to previous no voters. [...] The RFC contains a section with common criticism and objections to attributes, and I hope to have collected and responded to a good amount already from previous discussions.
Eberlei was successful in getting his proposal accepted by a 51 to 1 vote in favor. Additionally, the PHP internals community voted on the syntax that these new attributes would use. They were asked to select between two choices: << >> and @:. Both choices allow for attributes to have function-like arguments that would also be parsed to be used in a yet-to-be-determined way. Here is an example of how these would look in code, taken from the proposal:
/* Using the << >> syntax */
<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() { /* ... */}
/* Using the @: syntax */
@:WithoutArgument
@:SingleArgument(0)
@:FewArguments('Hello', 'World')
function foo() { /* ... */
Ultimately, the community voted to accept the << >> syntax over @: in a 41 to 12 vote, and the matter was believed to be resolved.
The fight over syntax
Not all of the community was satisfied with the decision on syntax, however, and last month Theodore Brown (with Martin Schröder) published another proposal to change the syntax from the previously accepted << >> syntax to @@ or #[] as shown:
/* Using the @@ syntax */
@@FewArguments('Hello', 'World')
function foo() { /* ... */}
/* Using the #[] syntax */
#[FewArguments('Hello', 'World')]
Function foo() { /* ... */ }
In the proposal, Brown itemized several faults he found with the accepted attribute syntax, among them were: its verbosity, its inability to support nested attributes, its dissimilarity with other languages that implement similar constructs, and potential confusion with bit-shifting operations.
The proposal included a vote to first decide if a change should even be
considered, followed by a ranked-choice vote on the preference for the new
syntax. The options provided by the proposal for the new syntax were:
<< >> (the original), @@, or #[].
Brown's proposal passed with a 50 to 8 vote in favor of changing the syntax.
The top choice for the new syntax was @@, followed by <<
>>, and finally #[]. Brown's proposal succeeded in
changing the syntax to @@, and again the community moved forward,
believing the matter to be resolved. As of the first alpha versions of PHP 8.0, the @@ syntax for attributes remained — until Rethans
interjected. Rethans decided to throw the aforementioned wrench into the
works with an
email to internals on July 22 (less than two weeks before the PHP 8.0
feature freeze) entitled "The @@ is terrible, are we sure we're OK with
it?
":
I know we've voted twice on this already, but are we really sure that the @@ syntax is a good idea?
- There are lots of grumbles, both on here, room 11 [A Stack Exchange chat room], as well as in the wider community (https://www.reddit.com/r/PHP/comments/hjpu79/it_is/)
- It has the distinct possibility to cause further parsing issues, akin to what ended up happening with what Nikita is addressing at https://wiki.php.net/rfc/namespaced_names_as_token
- There is no "end symbol" to make finding [occurrences] easier.
- It is a syntax no other language uses.
- @ is never going to go away, so the possibility of @@ moving to @ is also 0.
Please, let's do the sensible and use the Rusty #[...] syntax.
In the discussion that followed, Brown pushed back against yet another vote on syntax and challenged Rethans's complaints:
Most of the comments in that Reddit thread appear to be positive or neutral, and in the earlier community poll, the @@ syntax received by far the most votes (https://www.reddit.com/r/PHP/comments/h06bra/community_poll_attribute_syntax/). So while some in the community may prefer a different syntax, this doesn't seem to reflect the majority.
If @@ actually has parsing issues, then I would agree that we need to pick another syntax. But there aren't any issues following Nikita's RFC to treat namespaced names as tokens. Please check out the implementation and let me know if you find any problems: https://github.com/php/php-src/pull/5796.
As for Rethans's suggestion to use the #[] syntax, Brown further pointed out that the proposal had already been made and rejected:
The benefit of #[] being exactly the same as another language was considered in the Shorter Attribute Syntax RFC, but apparently most voters didn't feel that this outweighed the downsides of a larger [backward compatibility] break, extra verbosity, and possible confusion with comments.
Ultimately, release manager for PHP 8.0, Sara Golemon,
weighed in on the matter and challenged Rethans to back up his claims
that the syntax would lead to "further parsing issues
". With PHP 8.0 feature freeze scheduled less than two weeks away, Golemon said she
needed "something more substantial than 'it maybe breaks something
somewhere somehow'
" to consider revisiting attribute syntax. Golemon
did seem to give Rethans the benefit of the doubt regarding these parser
issues, however, saying "I'm not doubting that there is one, you're
quite clever, but at the moment you're stating facts not currently in
evidence.
"
After what appears to have been conversations that took place outside of the regular internals mailing list, Golemon followed up on her post to share what she had learned regarding Rethans's supposed parser issues:
So evidently, this is specifically an issue with attributes in places where a portion of their name could be mistaken for a type. AIUI [As I understand it], that's being addressed.
[...] Regards the vote; I don't believe that @@ has been proven unworkable, however if I'm wrong about that, then the second choice selection from the last vote would obviously take precedence.
In the lengthy conversation that followed, which included Golemon suggesting that attributes be removed from PHP 8.0 and moved to PHP 8.1, she decided to give everyone time to sort out the mess:
I'm willing to extend an additional period (up to the tagging of beta3, in just under six weeks) for a re-vote on the syntax as changing that will be less violent of a change than merging the entire implementation after branching.
I hope this solution is both procedurally appropriate and satisfactory to all.
Where does that leave attributes?
The PHP 8.0 feature freeze happened as scheduled; in the meantime, Rethans (with the help of contributor Joe Ferguson) submitted yet another proposal for attribute syntax. According to Ferguson:
An important part of the research that went into this proposal demonstrates how '@@' will make handling attributes more difficult for PHP_CodeSniffer (and similar tools) and shows how the alternative '#[]' syntax is more friendly for these tools.
PHP_CodeSniffer is a static-analysis tool for PHP to detect and correct violations of coding standards within a PHP code base. As might be expected, Brown continued to argue against another change with support from others. In contrast, other longstanding members of the PHP internals community like Paul M. Jones seem to be uninterested with re-voting on the matter. In response to the new proposal, Jones wrote:
But if we are to make decisions by what is ostensibly a democratic process, we should stick to the voted decisions, instead of re-voting issues until the voters "get it right" according to some implicit and unstated criteria. (If re-voting over and over is the plan, I've got some RFCs I might like to revisit as well.)
Ultimately Eberlei, as the original proposer of the implementation of attributes that the community accepted, managed to find common ground between the parties by offering a rather complicated solution using the single transferable vote (STV) system:
As the author of the original RFC and patch, I hope I have some [clout] in suggesting the following procedure (RMs [release managers] would need to extend their approval for revote to this).
- we collect syntax proposals once again, with the requirement of a simple patch being available against php-src/master for viability in 8.0.
- RMs are the arbiter to decide the patch is acceptable to be included for 8.0 or if its selection would delay entire attributes to 8.1.
- I would make a feature matrix for the vote / RFC page and sort each proposed syntax into it, seeking input from the proposers.
- We would then hold another vote on syntax using STV where the choice is a combination of syntax and target version, examples:
- <<>> in 8.0, #[] in 8.0, @@ in 8.0 (all these patches are viable)
- @@ in doc blocks for 8.1
- §[] in 8.1 (weird example to demonstrate the point)
and so on. The STV vote would run with potentially 5-10 different syntaxes.
On Time Frame: Sara allowed to extend this decision into feature freeze, but I believe it shouldn't be later than Beta 2 (August 20th), especially if the outcome could be delay until 8.1.
Both Rethans and Ferguson agreed with Eberlei's suggestion and updated the proposal to reflect the proposed voting matrix. Meanwhile, the community is waiting for the release managers to make a decision on Eberlei's suggestion. With Rethans on board, it is unlikely the release managers will stand in the way. Unfortunately for everyone, however, the conflict over the syntax of attributes has left a significant language feature slated for PHP 8.0 up in the air for the time being. The general consensus is that no one in the community wants the feature to be delayed until PHP 8.1, but preventing that delay will require a meeting of the minds that has been elusive on this issue — only time will tell where things end up.
Page editor: Jonathan Corbet
Next page:
Brief items>>
