The Thorn Programming Language: Robust Concurrent...
Transcript of The Thorn Programming Language: Robust Concurrent...
![Page 1: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/1.jpg)
The Thorn Programming Language: Robust
Concurrent Scripting
IBM Research
Bard Bloom
Jakob Dam John Field
Purdue
Brian Burg
Peter Maj Gregor Richards
Jan Vitek
Cambridge
Rok Strni!a
Texas, Arlington
Nate Nystrom
Stockholm University
Johan Östlund
Tobias Wrigstad
Java Languages Summit 2010 © IBM 2010
![Page 2: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/2.jpg)
Do these apps have anything in common?
2
cloud-based web 2.0
embedded network
real-time data analysis
![Page 3: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/3.jpg)
Yes
•! Collection of distributed, concurrent components
•! Components are loosely coupled by messages, persistent data
•! Irregular concurrency, driven by real-world data (“reactive”)
•! High data volumes
•! Fault-tolerance important 3
![Page 4: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/4.jpg)
Example: Twitter
4
search indexer
web gateway
page handler
page handler
page handler
page handler
user acct DB
tweet backup DB
memcache partition
memcache partition
memcache partition
mobile gateway
mobile gateway
advertising feed
•! each solid box is a logical process / event handler
•! each dashed line is a message
![Page 5: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/5.jpg)
Thorn goals
An open source, agile, high performance language for concurrent/distributed applications
and reactive systems
Key research directions
–! Code evolution: language, runtime, tool support for transition from prototype scripts to robust apps
–! Efficient compilation: for a dynamic language on a JVM
–! Cloud-level optimizations: high-level optimizations in a distributed environment
–! Security: end-to-end security in a distributed setting
–! Fault-tolerance: provide features that help programmers write robust code in the presence of hardware/software faults
5
![Page 6: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/6.jpg)
Features, present and absent
Features •! isolated, concurrent,
communicating processes •! lightweight objects •! first-class functions •! explicit state... •! ...but many functional
features •! powerful aggregate
datatypes •! expressive pattern matching •! dynamic typing •! lightweight module system •! JVM implementation and
Java interoperability •! gradual typing system
(experimental)
Non-features
•! changing fields/methods of objects on the fly
•! introspection/reflection
•! serialization of mutable objects/references or unknown classes
•! dynamic code loading
6
![Page 7: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/7.jpg)
Status
•! Open source: http:\\www.thorn-lang.org •! Interpreter for full language •! JVM compiler for language core
–! no sophisticated optimizations –! performance comparable to Python –! currently being re-engineered
•! Initial experience –!web apps, concurrent kernels, compiler, ...
•! Prototype of (optional) type annotation system
7
![Page 8: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/8.jpg)
Simple Thorn script
8
for (l <- argv()(0).file().contents().split("\n"))
if (l.contains?(argv()(1))) println(l);
file i/o methods
no explicit decl needed for var
split string into list
iterate over elements of a list
access command-line args
usual library functions on lists
![Page 9: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/9.jpg)
DEMO
grep
9
![Page 10: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/10.jpg)
More complex example: a MMORPG*
•! Adverbial ping-pong
•! Two players
•! Play by describing how you hit the ball
•! Distributed
•! Each player runs exactly the same code
*minimalist multiplayer online role-playing game 10
![Page 11: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/11.jpg)
DEMO
MMORPG
11
![Page 12: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/12.jpg)
MMORPG message flow
Player 1 Player 2
happily
eagerly
quickly
sluggishly
snickering
bouncing it off her head
12
![Page 13: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/13.jpg)
MMORPG Code
13
// MMORPG code for both players!
spawn {!
var done := false;!
body { ! [name, otherURI] = argv();! otherSite = site(otherURI);!
fun play(hit) {! advly = readln("Hit how?");! done := advly == "";! if (done) {! println("You lose!");! otherSite <<< null;! }! else {! otherSite <<< ! "$name $`hit`s the ball $advly.";! }! }!
start =! thisSite().str < otherSite.str;!
if (start) play("serve");!
do {! receive {! msg:string => {! println(msg);! play("return");! }! | null => {! println("You win!");! done := true;! }! }! } until (done);! }!
};!
spawn an isolated component (process)
mutable component-scoped variable
function decl
send a message (any immutable datum)
convert URI into component ref
receive messages matching pattern
pattern variable (with type constraint)
interpolate data into string
constant pattern
immutable component-scoped variable
![Page 14: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/14.jpg)
Thorn design philosophy •! Steal good ideas from everywhere
–! (ok, we invented some too) –! aiming for harmonious merge of features –! strongest influences: Erlang, Python (but there are many
others)
•! Assume concurrency is ubiquitous –! this affects every aspect of the language design
•! Adopt best ideas from scripting world... –! dynamic typing, powerful aggregates, ...
•! ...but seduce programmers to good software engineering –! powerful constructs that provide immediate value –! optional features for robustness –! encourage use of functional features when appropriate –! no reflective or self-modifying constructs
•! Syntax follows semantics –! more consequential ops have heavier syntax
14
![Page 15: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/15.jpg)
Scripting + concurrency: ? …or… !
•! Scripts already handle concurrency (but not especially well)
•! Dynamic typing allows code for distributed components to evolve independently…code can bend without breaking
•! Rich collection of built-in datatypes allows components with minimal advance knowledge of one another’s information schemas to communicate readily
•! Powerful aggregate datatypes extremely handy for managing component state
–! associative datatypes allow distinct components to maintain differing “views” of same logical data
15
![Page 16: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/16.jpg)
Thorn app: birdseye view
16
Site A
component 1
component 2
component 3
component 4
Site B
component 5
component 6
component 7
component 8
•! sites model physical application distribution
•! one JVM per site
•! I/O and other resources managed by sites
•! failures managed by sites
•! components are Thorn processes
•! components can spawn other components (at the same site)
•! processes communicate by message passing
•! intra- and inter-site messaging works the same way
![Page 17: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/17.jpg)
17
Anatomy of a component
component
module ... module
(optional channel definitions)
body
message queue (bag)
message
•! statement executed when component is spawned (usually a loop)
•! component execution ends when body ends
•! defines the component’s code and state
•! loaded and initialized when component is spawned
•! all program state encapsulated in one or more components
•! each component has a single thread of control
•! components are isolated
–! no shared state
–! exceptions do not propagate across components
•! messages passed by value
•! component refs are only form of remote reference
•! messages managed via a simple “mailbox” queue
•! no locks (in user code)
![Page 18: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/18.jpg)
module webhtml;!
import webschemas.html1strict;!
enforce_validity = true;!var xhtmlchecker := null;!
class Element(! var content:list, attr, type) {!
def str() =! "".join(%[ c.str | for c <- content ]);! ...!}!
fun head(args, content) =! Element(args, content, "head");! | head(content) =! Element({: :}, content, "head");!
Modules
18
bind immutable state variable
initialize mutable state variable
define class (which may access values of previous definitions)
define function (here, overloaded)
(selectively) import definitions from other modules
•! modules are reusable bundles of definitions –! of code
•! functions •! classes
–! of state •! mutable •! immutable
•! modules may import other modules
•! modules loaded (only) when enclosing component is spawned
•! set of modules used by any component defined statically
•! modules are initialized once, on first import
![Page 19: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/19.jpg)
Thorn data taxonomy primitive object: data/
method bundle
user-defined object
class-defined
anonymous
class
javaly
function built-in
immutable primitive
null
int
string
char
component ref
...
immutable aggregate
list
record
mutable aggregate
table
map
ordered
19
classes are generators of objects, not types (per se)
![Page 20: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/20.jpg)
More robust scripting
•! No reflection, eval, dynamic code loading –! alternatives for most scenarios
•! Ubiquitous patterns –! for documentation –! to generate efficient code
•! Powerful aggregates –! allow semantics-aware optimizations
•! Easy upgrade path from simple scripts to reusable code –! simple records " encapsulated classes
•! Channel-style concurrency –! to document protocols
•! Modules –! easy to wrap scripts, hide names
•! Experimental gradual typing system
20
![Page 21: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/21.jpg)
Thorn patterns
21
alist = [ [1, true], [15, null], ["yes", "no"] ];!
fun lookup(k, [ [$(k), v], _... ]) = +v;!
| lookup(k, []) = null;!
| lookup(k, [_, t...]) = lookup(k, t);!
if ( lookup(15, alist) ~ +w ) // found it!
match value of k! declare and bind variable y!
match arb. tail
“I found it, and it’s y!”
“I didn’t find it”
idiom for “did you find something (call it w)?”
Patterns are everywhere •! fun f(Pat1 ... Patn)
•! Pat = Exp
•! match(Exp) {Pat1 ... Patn}
•! receive {Pat1 ... Patn}
![Page 22: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/22.jpg)
Lists, queries
•! %[ Z | for i <- E ] !
–! list of the values of Z varying i
–! this one makes a list of random numbers
fun roll(nDice, nSides) = !
%[ nSides.rand1 | for i <- 1 .. nDice ].sum;
22
random number in 1 to nSides!
list method (nullary, hence may omit parens)!
![Page 23: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/23.jpg)
Records and tables
•! Tables are high power maps/dictionaries
•! Each row of a table is a record
•! Can add/delete rows
•! Adding a new column is easy; no need for objects or parallel tables
•! Variants: ordered (extensible arrays), map-style
•! Wide selection of queries
chirps = table(num){chirp; var plus, minus};!
...!
chirps(n) := {: chirp:c, plus:p, minus:m :}!
23
key! values! var = conveniently update one field “in place”!
update row with key n (other ops check if row already exists)
![Page 24: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/24.jpg)
Records to objects •! Prototype with records
•! Upgrade later to classes
•! And things still work
–! access via selectors
–! access via pattern matching
•! Plus, you get method calls
r = {: a:1, b:2 :}!
class Abc(a,b) { def aplusb() = a + b; };!
...!
r = Abc(1, 2);!
r.b == 2!
if (r ~ {: a :}) println(a);!
r.aplusb() == 3! 24
![Page 25: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/25.jpg)
Channel-style communication
25
{
}
sync chirp!(text, user) {
// sender blocks awaiting reply
}
async stopRightNow() {
// sender expects no reply
}
...
body {
while (true) serve;
}
component
synchronous communication
asynchronous communication
body runs immediately after component is spawned
process one message
Channels are sugar on basic messaging primitives
![Page 26: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/26.jpg)
Compiling Thorn to the JVM
Message dispatch –!compiler generates a Java Interface per method
signature (name/arity)
–!a Thorn class is compiled to a Java class that implements as many interfaces as it has methods
–!dispatch compiles to a cast operation following by an interface dispatch
–!number of interfaces can be reduced by grouping methods together in batches
26
![Page 27: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/27.jpg)
Compiling Thorn to the JVM
Fields –!every Thorn field access is compiled to a Java
method call
–!all fields are compiled to private fields in Java
–!all inherited fields are re-declared in each generated Java class
–!setter methods for val fields throw exceptions
27
![Page 28: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/28.jpg)
Optimizing Thorn
•! 87 bytecode instructions, 8 new frames, 8 new objects
fun a(i, j) = " 1.0 / (((i + j) * (i + j + 1) >> 1) + i + 1);
•! 29 bytecode instructions, 0 new frames, 1 new object (because of untyped return)
fun a(i: int, j: int) =" 1.0 / (((i + j) * (i + j + 1) >> 1) + i + 1);
28
![Page 29: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/29.jpg)
Performance
29
![Page 30: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/30.jpg)
A bigger app: WebCheeper
•! each solid box is an isolated Thorn component •! each dashed box is a Thorn site
30
HTTP gateway memcache
chirp indexer
page handler
page handler
page handler
page handler
component instantiated dynamically per HTTP request
twitter app API
![Page 31: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/31.jpg)
DEMO
WebCheeper
31
![Page 32: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/32.jpg)
page handler
page handler
page handler
page handler
page handler
page handler
page handler
page handler
page handler
page handler
WebCheeper deployed on AppScale cloud
32
HTTP gateway
twitter app API
page handler
HTTP gateway
HTTP gateway
page handler
AppScale request
dispatcher
memcache here, thorn components are replicated and deployed on additional sites for increased scalability
chirp indexer
inter-component and inter-site optimizations may be more consequential than than intra-component optimizations
![Page 33: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/33.jpg)
Thorn: research testbed
In progress •! optimizing compiler •! cloud-level optimizations
–! code, data placement –! serialization
–! message piggybacking •! component-level security
–! information flow
–! access control •! join-style patterns for
synchronization •! database integration
Planned •! lighter-weight Java
integration
•! refactored, componentized libraries
•! much more work on optional types
–! e.g., generalization of patterns
•! failure recovery for components
•! static checkers
33
![Page 34: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/34.jpg)
More information
•! http://www.thorn-lang.org –! download interpreter –! links to papers –! online demo
•! Additional collaborators welcome! •! Workshop (NB: moved to Wed, 11:30)
–! larger code examples –! more on
•! concurrency •! tables/queries •! classes •! optional types •! Java interop •! JSON/HTTP handling
–! some lessons learned –! discussion: role of languages in distributed/cloud apps
34
![Page 35: The Thorn Programming Language: Robust Concurrent Scriptingwiki.jvmlangsummit.com/images/a/a0/Field-Thorn-Overview-2010.pdf · AppScale cloud 32 HTTP gateway twitter app API page](https://reader034.fdocuments.net/reader034/viewer/2022042301/5ecc4302e2e77955c85a55e6/html5/thumbnails/35.jpg)
Questions?
35