Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread George Neuner


On 3/13/2017 5:16 PM, Ryan Culpepper wrote:


When a thread uses a VC, it gets an underlying connection assigned 
exclusively to it for the lifetime of the thread[*]. When the thread 
dies, the VC releases the underlying connection; if it came from a 
pool, it returns to the pool and it can be obtained by another thread.


[*] or until the thread calls disconnect


Ah.  My mistake.   This was not clear [to me] from the documentation.

Thanks,
George


--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread George Neuner


On 3/13/2017 3:16 PM, David Storrs wrote:


... you talk about the "real connection", singular -- aren't there 
multiple real connections underlying the pool?


Yes, a pool maintains some number of real connections to the DBMS.

Note, however, that a pool can be configured to maintain just a single 
connection:

(connection-pool ... #:max-connections 1 #:max-idle-connections 1)


- The pool will maintain some number (hopefully >0) of real 
connections.  Every time the virtual connection receives a request, 
that request will be forwarded to a real connections, creating the 
connection if necessary. (Presumably either the pool or the VC will 
take responsibility for verifying that the connection in question has 
not been disconnected by the RDBMS and generating a new one if it has.)


Is that right?


Yes.  In either case, new DBMS connections will be created on demand if 
needed.


However, a virtual connection effectively opens and closes  a real 
connection for EVERY query.   This will be the actual behavior if the VC 
is created using a connect function.  You really need to use a pool if 
actual DBMS connections are slow to establish.


The default open/close behavior generally is OK for a local database 
solution - e.g., Sqlite, or a DBMS server co-resident with your program 
on the same host.  But even with a co-resident DBMS server, it will work 
better if you can use Unix sockets rather than [loopback] TCP.


George

--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread Ryan Culpepper

On 03/13/2017 04:56 PM, George Neuner wrote:


On 3/13/2017 3:41 PM, David Storrs wrote:

On Mon, Mar 13, 2017 at 2:04 PM, Ryan Culpepper > wrote:

If you are using `prepare` just for speed, it might help to know that
most base connections have an internal statement cache that maps SQL
strings to prepared statement objects. The cache is only used inside
of transactions, though, to avoid issues with concurrent schema changes.

You can check on statement caching by setting the #:debug? argument
when connecting. Aside from lots of other noise, queries will print
out whether they are using the statement cache. Here's an example:

  > (define c (dsn-connect 'pg #:debug? #t))
  
  > (start-transaction c)
  
  ** in managed transaction
  > (query c "select 1")
  
  ** caching statement
  
  > (query c "select 1")
  ** using cached statement
  

Could that replace your use of `prepare`?



Looks like inside a transaction all statements are prepared.  Cool.
If so, that's very nearly perfect -- it adds and removes all the
concerns that transactions always add/remove, but offhand I can't
think of a case where doing it in a transaction would be a problem.


[...]

I'm not sure exactly what  #:debug?  is showing you.   Ryan knows better
than I do, but if you haven't explicitly called "prepare", then I
suspect it is only the specific syntax of the cached query that can be
reused, and that if you change the query in any way - e.g., by passing
different arguments - then the cached version will not be used.


Racket's db library always prepares a statement before executing it, 
even if there are no query parameters. When allowed, instead of closing 
the prepared statement immediately after executing it, the connection 
stores it in a (Racket-side, per-connection) cache. The cache key is 
just the SQL string. If you use the same query string with different 
parameters, you'll still get the cached prepared statement.


To extend my previous example:

  > (query c "select $1::integer" 1)
  
  ** caching statement
  
  > (query c "select $1::integer" 2)
  ** using cached statement
  

On the other hand, if the query string is not identical, it misses the 
cache.


Ryan

--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread David Storrs
Thank you, everyone.  I really appreciate the detailed answers.

On Mon, Mar 13, 2017 at 5:16 PM, Ryan Culpepper  wrote:

> On 03/13/2017 03:16 PM, David Storrs wrote:
>
>> [...]
>> On Mon, Mar 13, 2017 at 2:49 PM, George Neuner > > wrote:
>>
>>> - It's also fine to pass the VC into other threads.  It will be
>>> shared state between the threads, but the CP will keep their
>>> connections isolated and when the threads terminate it won't
>>> interfere.  (Ignore pathological cases -- obviously if I give it
>>> to enough threads and they all use it at once then we might exceed
>>> the DB limits on number of handles, speed, bandwidth, etc.)
>>>
>>
>> No.  A VC is not tied to any particular real connection, so it's not
>> possible to guarantee that 2 threads sharing a VC do not share an
>> RC.   It's actually quite likely in a lightly loaded system.
>>
>> If you need connection isolation, you have to use real connections.
>>
>> Gotcha.  Thanks.
>>
>
> Two threads that share a VC will not have the same real connection *at the
> same time*. When a thread uses a VC, it gets an underlying connection
> assigned exclusively to it for the lifetime of the thread[*]. When the
> thread dies, the VC releases the underlying connection; if it came from a
> pool, it returns to the pool and it can be obtained by another thread.
>
> [*] or until the thread calls disconnect
>
> [...]
>>
>> There is no way in the current implementation to get at the real
>> connection underlying the virtual one.  Because the system
>> multiplexes connections, I think it probably would break badly if
>> you were able to somehow get hold of the underlying real connection.
>>
>> I know this isn't what you want to hear.
>> George
>>
>> Hey, I'd rather hear uncomfortable truth than comforting lies.  Still,
>> in the above you talk about the "real connection", singular -- aren't
>> there multiple real connections underlying the pool?
>>
>> My mental model for how this works is:
>>
>> - User creates a function F that creates real connections
>>
>> - User hands F to a pool via: (virtual-connection (connection-pool F))
>> and gets back a VC.
>>
>> - The pool will maintain some number (hopefully >0) of real
>> connections.  Every time the virtual connection receives a request, that
>> request will be forwarded to a real connections, creating the connection
>> if necessary.  (Presumably either the pool or the VC will take
>> responsibility for verifying that the connection in question has not
>> been disconnected by the RDBMS and generating a new one if it has.)
>>
>> Is that right?
>>
>
> Yes, but the VC keeps a mapping of threads to connections as I mentioned
> above, so it doesn't choose a new real connection per query.
>
> The connection pool does check that a real connection is still connected
> before it hands it out, but there's a race: the server could disconnect
> between the check and when the connection actually gets used for the first
> time.
>
> Ryan
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread Ryan Culpepper

On 03/13/2017 03:16 PM, David Storrs wrote:

[...]
On Mon, Mar 13, 2017 at 2:49 PM, George Neuner > wrote:

- It's also fine to pass the VC into other threads.  It will be
shared state between the threads, but the CP will keep their
connections isolated and when the threads terminate it won't
interfere.  (Ignore pathological cases -- obviously if I give it
to enough threads and they all use it at once then we might exceed
the DB limits on number of handles, speed, bandwidth, etc.)


No.  A VC is not tied to any particular real connection, so it's not
possible to guarantee that 2 threads sharing a VC do not share an
RC.   It's actually quite likely in a lightly loaded system.

If you need connection isolation, you have to use real connections.

Gotcha.  Thanks.


Two threads that share a VC will not have the same real connection *at 
the same time*. When a thread uses a VC, it gets an underlying 
connection assigned exclusively to it for the lifetime of the thread[*]. 
When the thread dies, the VC releases the underlying connection; if it 
came from a pool, it returns to the pool and it can be obtained by 
another thread.


[*] or until the thread calls disconnect


[...]

There is no way in the current implementation to get at the real
connection underlying the virtual one.  Because the system
multiplexes connections, I think it probably would break badly if
you were able to somehow get hold of the underlying real connection.

I know this isn't what you want to hear.
George

Hey, I'd rather hear uncomfortable truth than comforting lies.  Still,
in the above you talk about the "real connection", singular -- aren't
there multiple real connections underlying the pool?

My mental model for how this works is:

- User creates a function F that creates real connections

- User hands F to a pool via: (virtual-connection (connection-pool F))
and gets back a VC.

- The pool will maintain some number (hopefully >0) of real
connections.  Every time the virtual connection receives a request, that
request will be forwarded to a real connections, creating the connection
if necessary.  (Presumably either the pool or the VC will take
responsibility for verifying that the connection in question has not
been disconnected by the RDBMS and generating a new one if it has.)

Is that right?


Yes, but the VC keeps a mapping of threads to connections as I mentioned 
above, so it doesn't choose a new real connection per query.


The connection pool does check that a real connection is still connected 
before it hands it out, but there's a race: the server could disconnect 
between the check and when the connection actually gets used for the 
first time.


Ryan

--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread George Neuner


On 3/13/2017 3:41 PM, David Storrs wrote:
On Mon, Mar 13, 2017 at 2:04 PM, Ryan Culpepper > wrote:
If you are using `prepare` just for speed, it might help to know that 
most base connections have an internal statement cache that maps SQL 
strings to prepared statement objects. The cache is only used inside 
of transactions, though, to avoid issues with concurrent schema changes.


You can check on statement caching by setting the #:debug? argument 
when connecting. Aside from lots of other noise, queries will print 
out whether they are using the statement cache. Here's an example:


  > (define c (dsn-connect 'pg #:debug? #t))
  
  > (start-transaction c)
  
  ** in managed transaction
  > (query c "select 1")
  
  ** caching statement
  
  > (query c "select 1")
  ** using cached statement
  

Could that replace your use of `prepare`?



Looks like inside a transaction all statements are prepared.  Cool.  
If so, that's very nearly perfect -- it adds and removes all the 
concerns that transactions always add/remove, but offhand I can't 
think of a case where doing it in a transaction would be a problem.


There's a difference between "prepared" and "compiled".

Every query is compiled.  The compiled version can be - and typically is 
in most DBMS - cached for reuse if the same *identical* query string is 
presented again.  With a normal query, the compiled version is optimized 
based on the specific values contained in the statement.


Prepare is different.  The idea of "preparation" is to be able to reuse 
a generic query with different arguments.  A prepared query is compiled 
to use variables rather than inline values, so it is not as well 
optimized as compilation of a normal query.  The savings in preparation 
comes mainly from avoiding compiles on subsequent reuse.


The compiled version of a prepared query persists until explicitly 
dropped or the connection is closed.  It is not cached in the same way 
as are normal queries.



I'm not sure exactly what  #:debug?  is showing you.   Ryan knows better 
than I do, but if you haven't explicitly called "prepare", then I 
suspect it is only the specific syntax of the cached query that can be 
reused, and that if you change the query in any way - e.g., by passing 
different arguments - then the cached version will not be used.



George

--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread David Storrs
On Mon, Mar 13, 2017 at 2:04 PM, Ryan Culpepper  wrote:

> On 03/13/2017 12:49 PM, David Storrs wrote:
>
>> [...]
>>
>>
>> Assuming I've understood all that correctly, my last question would be
>> how to get around the 'can't do prepare with a virtual connection' issue
>> for situations where I've been passed a connection (perhaps from third
>> party code) and it might or might not be virtual. First, to dispose of
>> some quibbles:
>>
>> - One answer is "well, don't do that."  Write a contract on the function
>> that mandates the connection being non-virtual.
>>
>> - Another is "well, don't do that."  Test if the connection is virtual
>> and, if so, don't use prepare.
>>
>> - Another is "well, don't do that."  Pass around a dsn instead of a VC
>> and generate connections as needed.
>>
>> None of these is terribly satisfying.  The first violates the principle
>> of "be generous in what you accept and strict in what you emit", the
>> second gives up a lot of speed if we were in a situation where we wanted
>> to use 'prepare' in the first place, and the third isn't feasible since
>> I won't always have control over what a third-party library emits.
>>
>>
>> My ideal solution would be something like this:
>>
>> (define (myfunc a-handle)
>> (define dbh
>> (if (virtual-connection? a-handle)
>> (my-function-to-do-connect-with-dsn (get-dsn-from a-handle))
>> a-handle))
>> (define sth (prepare dbh "select foo from bar where baz = $1"))
>> ...stuff...
>> )
>>
>> In other words, check if the connection is virtual and, if so, extract
>> the dsn from it and use that to create a non-virtual connection.
>>
>> Is there a way to do that?  I've been through the db module docs and
>> Google but not found a way.  Did I miss something?
>>
>
> There's no way to do that at the moment.
>
> If you are using `prepare` just for speed, it might help to know that most
> base connections have an internal statement cache that maps SQL strings to
> prepared statement objects. The cache is only used inside of transactions,
> though, to avoid issues with concurrent schema changes.
>
> You can check on statement caching by setting the #:debug? argument when
> connecting. Aside from lots of other noise, queries will print out whether
> they are using the statement cache. Here's an example:
>
>   > (define c (dsn-connect 'pg #:debug? #t))
>   
>   > (start-transaction c)
>   
>   ** in managed transaction
>   > (query c "select 1")
>   
>   ** caching statement
>   
>   > (query c "select 1")
>   ** using cached statement
>   
>
> Could that replace your use of `prepare`?
>



Looks like inside a transaction all statements are prepared.  Cool.  If so,
that's very nearly perfect -- it adds and removes all the concerns that
transactions always add/remove, but offhand I can't think of a case where
doing it in a transaction would be a problem.

Thanks, Ryan.




>
> Ryan
>
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread David Storrs
On Mon, Mar 13, 2017 at 2:49 PM, George Neuner  wrote:

>
> This is why you can't use prepared queries with virtual connections -
> because there is no guarantee that the connection that prepared the query
> will be the same one that tries to execute it.
>

*nod*  Good, that's what I thought.  Nice to have it confirmed.


>
>
> - The connection pool (CP) inside the VC will maintain a (small?)
> collection of persistent connections to hand out upon request, so I'm not
> paying the connection-setup-time penalty every time I use it.
>
>
> Yes and no.  The pool will try to keep idle connections, but the DBMS
> eventually will close them  (subject to the keep-alive settings in
> postgresql.conf).
>

Ah, makes sense.  Still, the point is that in the normal case events I
shouldn't need to pay the setup cost each time.



>
> - It's also fine to pass the VC into other threads.  It will be shared
> state between the threads, but the CP will keep their connections isolated
> and when the threads terminate it won't interfere.  (Ignore pathological
> cases -- obviously if I give it to enough threads and they all use it at
> once then we might exceed the DB limits on number of handles, speed,
> bandwidth, etc.)
>
>
> No.  A VC is not tied to any particular real connection, so it's not
> possible to guarantee that 2 threads sharing a VC do not share an RC.
> It's actually quite likely in a lightly loaded system.
>
> If you need connection isolation, you have to use real connections.
>

Gotcha.  Thanks.



> Real connections will persist until closed.  Virtual connections can come
> and go like dreams.
>

If this were a forum I would sig that.  :>


> There is no way in the current implementation to get at the real
> connection underlying the virtual one.  Because the system multiplexes
> connections, I think it probably would break badly if you were able to
> somehow get hold of the underlying real connection.
>
>
> I know this isn't what you want to hear.
> George
>

Hey, I'd rather hear uncomfortable truth than comforting lies.  Still, in
the above you talk about the "real connection", singular -- aren't there
multiple real connections underlying the pool?

My mental model for how this works is:

- User creates a function F that creates real connections

- User hands F to a pool via: (virtual-connection (connection-pool F)) and
gets back a VC.

- The pool will maintain some number (hopefully >0) of real connections.
Every time the virtual connection receives a request, that request will be
forwarded to a real connections, creating the connection if necessary.
(Presumably either the pool or the VC will take responsibility for
verifying that the connection in question has not been disconnected by the
RDBMS and generating a new one if it has.)

Is that right?




> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread George Neuner

Hi David,

On 3/13/2017 12:49 PM, David Storrs wrote:

I've got a centralized database connector:

(define dbh
 (virtual-connection
(connection-pool
 (thunk (postgresl-connect ...)

This gets passed around from handle to handle and into various 
temporary or ongoing worker threads.  Thinking about it, I'd like to 
check that I've understood it properly:


- The virtual connection (VC) itself is very lightweight and it's fine 
for it to persist throughout the run of the program.


Yes.  However, virtual connections are multiplexed over real 
connections.  The underlying real connection  can be stolen (given to 
another virtual connection) any time it is idle - such as when it is 
between queries.  If you have a thread that performs a sequence of queries:


  :
do_query_1
  :
do_query_2
  :

then it's possible for the real connection to be snatched away between 
the queries.  Of course, when the thread goes to use the VC again, it 
will get *some* real connection, but there's no guarantee that it will 
be the *same* one each time.


This is why you can't use prepared queries with virtual connections - 
because there is no guarantee that the connection that prepared the 
query will be the same one that tries to execute it.



- The connection pool (CP) inside the VC will maintain a (small?) 
collection of persistent connections to hand out upon request, so I'm 
not paying the connection-setup-time penalty every time I use it.


Yes and no.  The pool will try to keep idle connections, but the DBMS 
eventually will close them  (subject to the keep-alive settings in 
postgresql.conf).



- It's also fine to pass the VC into other threads.  It will be shared 
state between the threads, but the CP will keep their connections 
isolated and when the threads terminate it won't interfere.  (Ignore 
pathological cases -- obviously if I give it to enough threads and 
they all use it at once then we might exceed the DB limits on number 
of handles, speed, bandwidth, etc.)


No.  A VC is not tied to any particular real connection, so it's not 
possible to guarantee that 2 threads sharing a VC do not share an RC.   
It's actually quite likely in a lightly loaded system.


If you need connection isolation, you have to use real connections.


- The CP will create more connections as needed, so there's no need to 
worry about running out (barring pathological cases).


Subject to limitations.  You can't open more real connections than the 
DBMS permits.  However, you can have many more virtual connections than 
real connections.



- db connections will get garbage collected normally, at the marked 
points:


I have to defer this to someone who knows for sure.  Real connections 
will persist until closed.  Virtual connections can come and go like dreams.



Assuming I've understood all that correctly, my last question would be 
how to get around the 'can't do prepare with a virtual connection' 
issue for situations where I've been passed a connection (perhaps from 
third party code) and it might or might not be virtual. First, to 
dispose of some quibbles:


- One answer is "well, don't do that."  Write a contract on the 
function that mandates the connection being non-virtual.


- Another is "well, don't do that."  Test if the connection is virtual 
and, if so, don't use prepare.


- Another is "well, don't do that."  Pass around a dsn instead of a VC 
and generate connections as needed.


None of these is terribly satisfying.  The first violates the 
principle of "be generous in what you accept and strict in what you 
emit", the second gives up a lot of speed if we were in a situation 
where we wanted to use 'prepare' in the first place, and the third 
isn't feasible since I won't always have control over what a 
third-party library emits.



My ideal solution would be something like this:

(define (myfunc a-handle)
(define dbh
(if (virtual-connection? a-handle)
(my-function-to-do-connect-with-dsn (get-dsn-from a-handle))
a-handle))
(define sth (prepare dbh "select foo from bar where baz = $1"))
...stuff...
)

In other words, check if the connection is virtual and, if so, extract 
the dsn from it and use that to create a non-virtual connection.


Is there a way to do that?  I've been through the db module docs and 
Google but not found a way.  Did I miss something?




Even if you could discover the DSN, it would not help if the DBMS 
connection limit is reached,  or equivalently some user limit imposed by 
a proxy server that you don't know about.


There is no way in the current implementation to get at the real 
connection underlying the virtual one.  Because the system multiplexes 
connections, I think it probably would break badly if you were able to 
somehow get hold of the underlying real connection.



I know this isn't what you want to hear.
George

--
You received this message because you are subscribed to the Google Groups 

Re: [racket-users] Virtual connections, threads, and DSN extraction, oh my!

2017-03-13 Thread Ryan Culpepper

On 03/13/2017 12:49 PM, David Storrs wrote:

[...]

Assuming I've understood all that correctly, my last question would be
how to get around the 'can't do prepare with a virtual connection' issue
for situations where I've been passed a connection (perhaps from third
party code) and it might or might not be virtual. First, to dispose of
some quibbles:

- One answer is "well, don't do that."  Write a contract on the function
that mandates the connection being non-virtual.

- Another is "well, don't do that."  Test if the connection is virtual
and, if so, don't use prepare.

- Another is "well, don't do that."  Pass around a dsn instead of a VC
and generate connections as needed.

None of these is terribly satisfying.  The first violates the principle
of "be generous in what you accept and strict in what you emit", the
second gives up a lot of speed if we were in a situation where we wanted
to use 'prepare' in the first place, and the third isn't feasible since
I won't always have control over what a third-party library emits.


My ideal solution would be something like this:

(define (myfunc a-handle)
(define dbh
(if (virtual-connection? a-handle)
(my-function-to-do-connect-with-dsn (get-dsn-from a-handle))
a-handle))
(define sth (prepare dbh "select foo from bar where baz = $1"))
...stuff...
)

In other words, check if the connection is virtual and, if so, extract
the dsn from it and use that to create a non-virtual connection.

Is there a way to do that?  I've been through the db module docs and
Google but not found a way.  Did I miss something?


There's no way to do that at the moment.

If you are using `prepare` just for speed, it might help to know that 
most base connections have an internal statement cache that maps SQL 
strings to prepared statement objects. The cache is only used inside of 
transactions, though, to avoid issues with concurrent schema changes.


You can check on statement caching by setting the #:debug? argument when 
connecting. Aside from lots of other noise, queries will print out 
whether they are using the statement cache. Here's an example:


  > (define c (dsn-connect 'pg #:debug? #t))
  
  > (start-transaction c)
  
  ** in managed transaction
  > (query c "select 1")
  
  ** caching statement
  
  > (query c "select 1")
  ** using cached statement
  

Could that replace your use of `prepare`?

Ryan

--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.