7 de setembro de 2009

Cronologia dos 40 anos da internet

Na semana passada, a internet se tornou uma quarentona. No dia 2 de setembro de 1969 foi realizado com sucesso a primeira troca de dados entre dois computadores numa rede militar experimental - a Arpanet. Embora os dados trocados não tivessem qualquer sentido, a tecnologia desenvolvida deu o pontapé inicial que culminou na atual rede mundial de computadores, com todas as suas consequências. Não vou fazer aqui um tratado sociológico do impacto da internet; apenas reproduzirei a cronologia dos principais eventos envolvendo-a. O texto original foi produzido pela Associated Press e segue conforme foi publicado na Folha online:
1969: Em 2 de setembro, dois computadores na UCLA (Universidade da Califórnia, Los Angeles) trocam dados sem sentido no primeiro teste da Arpanet, uma rede militar experimental. A primeira conexão entre dois locais --a UCLA e a Stanford Research Institute, também na Califórnia-- acontece em 29 de outubro, apesar de a rede ser interrompida após digitarem as duas primeiras letras da palavra "logon". A Universidade da Califórnia Santa Bárbara e a Universidade de Utah também se juntam à rede depois.

1970: A Arpanet chega à sua primeira ligação na costa leste dos Estados Unidos, na empresa Bolt, Beranek e Newman --agora BBN Technologies--, em Cambridge, Massachusetts.

1972: Ray Tomlinson traz também o e-mail à rede, escolhendo o símbolo "at" ou "@" como maneira de especificar endereços de e-mail pertencendo a outros sistemas.

1973: A Arpanet ganha suas primeiras ligações internacionais, na Inglaterra e Noruega.

1974: Vint Cerf e Bob Kahn desenvolvem a técnica de comunicações TCP, permitindo que múltiplas redes se compreendam, criando a verdadeira internet. Posteriormente, o conceito se divide em TCP/IP antes de sua adoção formal, em 1º de janeiro de 1983.

1983: O DNS (Domain Name System) é proposto. A criação de sufixos como ".com", ".gov" e ".edu" chega um ano depois.

1988: Um dos primeiros worms da internet, Morris, causa danos a milhares de computadores.

1989: A Quantum Computer Services, agora AOL, inaugura o serviço America Online para computadores Macintosh e Apple 2, começando uma expansão que acabaria por conectar cerca de 27 milhões de norte-americanos em 2002.

1990: Tim Berners-Lee cria a WWW (World Wide Web) enquanto desenvolvia maneiras de controlar computadores a distância na Cern (Organização Europeia para Pesquisa Nuclear).

1993: Marc Andreessen e colegas na Universidade de Illinois criam o Mosaic, primeiro navegador a combinar gráficos e texto em uma única página, abrindo a web para o mundo com um software fácil de usar.

1994: Andreessen e outros na equipe do Mosaic formam uma empresa para desenvolver o primeiro navegador comercial, o Netscape. Isso chama a atenção da Microsoft e de outros desenvolvedores que iriam investir no potencial comercial da web. Dois advogados da área de imigração apresentam o spam ao mundo, ao fazer propaganda de seus serviços de "green card lottery" --programa de distribuição de vistos norte-americanos.

1995: A Amazon.com abre suas portas virtuais.

1998: Google monta um projeto iniciado nos dormitórios de Stanford. O governo dos Estados Unidos delega a supervisão das políticas relacionadas a nomes de domínios para a Icann (Internet Corporation for Assigned Names and Numbers). O Departamento de Justiça e 20 Estados acusam a Microsoft, criadora do onipresente sistema operacional Windows de abusar de seu poder de mercado, frustrando a competição com o Netscape e outros.

1999: O Napster populariza o compartilhamento de arquivos de música, levando a sucessores que mudaram permanentemente a indústria das gravadoras. A população usuária de internet no mundo ultrapassa 250 milhões de pessoas.

2000: O "boom" das empresas de tecnologia dos anos 1990 dá lugar à explosão da bolha do setor. A Amazon.com, eBay e outros sites são seriamente prejudicados em um dos primeiros usos em larga escala do ataque de negação de serviço, que enche um site com tanto tráfico falso que usuários de verdade não conseguem visitá-lo.

2002: A população usuária de internet do mundo ultrapassa 500 milhões de pessoas.

2004: Marck Zuckerberg inicia o Facebook, em seu segundo ano de curso na Universidade Harvard.

2005: É inaugurado o site de compartilhamento de vídeos YouTube.

2006: A população usuária de internet do mundo ultrapassa 1 bilhão de pessoas.

2007: A Apple lança o iPhone, trazendo o acesso a internet sem fio a mais milhões de pessoas.

2008: Os usuários de internet do mundo ultrapassam 1,5 bilhões de pessoas. O total só na China chega a 250 milhões, ultrapassando os Estados Unidos como o país com a maior população usuária de internet do mundo. Os desenvolvedores do Netscape interrompem o navegador pioneiro, embora seu "sucessor", Firefox, permaneça forte. Importantes companhias aéreas intensificam o uso de serviços de internet nos voos.

2009: O "Seattle Post-Intelligencer" torna-se o primeiro grande jornal diário a ficar exclusivamente online. O Google anuncia o desenvolvimento de um sistema operacional com foco na web.


4 de setembro de 2009

Trabalhando com Threads em Delphi - Seções Críticas

Um dos aspectos mais importantes com que se preocupar quando desenvolvemos software envolvendo threads é o acesso a recursos compartilhados. Isto é, a forma com que vamos lidar com variáveis globais em memória, gravação e leitura de arquivos, impressão, acesso à interface visual, etc., de modo que seja garantido que todas as threads sempre encontrem esses recursos num estado apropriado para uso.

Pense, por exemplo, que você criou uma thread para imprimir relatórios em background para seu sistema. Você disponibiliza uma lista global onde o usuário pode ir adicionando novos relatórios a serem impressos. Sua thread de impressão busca nessa mesma lista qual é o próximo relatório a imprimir.

Antes de prosseguir, lembro alguns detalhes para tornar mais claro o contexto. Quando você escreve um programa em Delphi ou outra linguagem de alto nível, o compilador converte cada linha do seu programa em diversas instruções de baixo nível que podem ser executadas pelo computador. Toda thread é, então, composta por uma sequência das instruções originadas a partir das várias linhas do código que você escreveu. O Sistema Operacional executa as threads submetendo a sequência de instruções de cada uma delas por um tempo determinado (chamado time slice). Como esse tempo é determinado pelo Sistema Operacional, não há garantias de que todo o bloco gerado a partir da linha de código Delphi será executado todo de uma vez.
Operação de threads
Na figura acima, se o tempo destinado à Thread Principal se esgotar no ponto desenhado, o estado das variáveis de programa envolvidas pode não ter sido completamente atualizado. Outra Thread que acessar uma variável nessa condição encontrará lixo.

Voltando ao exemplo da thread de impressão, é preciso garantir que o código que manipula a lista seja executado como uma unidade. Sistemas Operacionais como o Windows disponibilizam um mecanismo denominado Seção Crítica para resolver esta questão. No Delphi e C++ Builder, a classe TCriticalSection (que está na unit syncobjs) encapsula o funcionamento desse mecanismo. A ideia é simples : cria-se uma instância de seção crítica para cada recurso que se queira proteger, proteção essa que se dá envolvendo o acesso ao recurso com a chamada a 2 funções - uma para entrar na seção crítica e a outra para liberar o acesso ao recurso novamente. Ao adicionar um novo Job à lista de impressão:
try
_scPrintJobs.Enter;
_Jobs.Add (pNovoJob);
finally
_scPrintJobs.Leave;
end;
E também quando a Thread responsável pela impressão for remover um job para executá-lo:
try
_scPrintJobs.Enter;
lJob := _Jobs.Items[pJobIdx];
_Jobs.Delete (pJobIdx);
finally
_scPrintJobs.Leave;
end;
É conveniente utilizar tratamento de exceções (par try/finally) pois a seção crítica é um recurso bastante sensível. Se ocorrer algum problema e o método Leave nunca for executado, o seu programa fatalmente vai deixar de funcionar. É bom usar com critério o quê vai ser colocado dentro da seção crítica pois um processamento pesado pode fazer com que o restante da aplicação pare de responder.

Por questão de organização, a criação da seção crítica pode ser feita na própria unit reservada ao recurso que ela deve proteger:
var _scPrintJobs : TCriticalSection;
{...}
initialization
_scPrintJobs := TCriticalSection.Create;

finalization
_scPrintJobs.Destroy;
end.
Seções Críticas devem ser utilizadas somente dentro de um mesma aplicação. Se pretende construir uma biblioteca (DLL) que poderá ser utilizada por mais de uma aplicação ao mesmo tempo, o mecanismo mais apropriado é o Mutex (acesso mutuamente exclusivo). Os Mutexes funcionam do mesmo modo que as seções críticas e, embora possam ser usados também quando o escopo é o mesmo processo, são ligeiramente mais lentos.

2 de setembro de 2009

Usando interfaces em Delphi - parte 3

Vou montar neste post um exemplo do uso de interfaces em Delphi, mostrando as formas que encontrei para evitar as armadilhas apontadas no post anterior. A ideia é modelar uma classe que seja capaz de ordernar uma lista sem se preocupar com qual é de fato o tipo do objeto que está sendo classificado - basta que ele implemente uma interface apropriada, que chamarei aqui de IWSortable.

O primeiro ponto é a necessidade de referenciar o objeto original que foi atribuído à interface - lembre-se que ponteiro para interface é incompatível com ponteiro para class. No caso específico do IWSortable, a função de comparação poderá fazer uso de qualquer das propriedades de uma classe de objetos para determinar a ordem correta de cada um dentro da lista. Se for trabalhar, por exemplo, com uma lista de Pessoas, a ordem poderá ser primeiro por nome, depois por idade e envolver quantos campos mais forem necessários.

Minha solução para isso é criar uma interface base com uma função que retorne o ponteiro para o objeto atual. Todas as minhas interfaces, então, herdarão dessa base:
IWBaseInterface = interface
['{97DF568E-D4ED-4F1A-99DF-07E524BD598D}']
function GetSelfObj : TObject;
end;

IWSortable = interface(IWBaseInterface)
['{7CEF3AE4-9127-460E-B1A1-8D18B7CA7D05}']
function CompareTo (AObj : IWSortable) : integer;
end;
Com isso, todas as classes que implementarem a interface serão capazes de reportar seu próprio ponteiro quando solicitadas. Para facilitar, também criei uma classe base para fornecer essa funcionalidade como padrão.
TWInterfacedObject = class(TObject, IWBaseInterface)
{...}
public
function GetSelfObj : TObject;
end;
{...}
function TWInterfacedObject.GetSelfObj : TObject;
begin
Result := Self;
end;
A função precisa apenas reportar o ponteiro Self para satisfazer a interface IWBaseInterface. Uma classe hipotética TWPessoa poderia implementar o método CompareTo recorrendo a esta função :
TWPessoa = class(TWInterfacedObject, IWSortable)
public
idade : integer;
nome : String;
function CompareTo (AObj : IWSortable) : integer;
end;
{...}
function TWPessoa.CompareTo (AObj : IWSortable) : integer;
var pessoa : TWPessoa;
begin
Result := 0;
pessoa := AObj.GetSelfObj As TWPessoa;

if (nome < pessoa.nome) then
result := -1;
if (nome > pessoa.nome) then
result := 1;

// se os nomes são iguais, ordena pela idade
if (Result = 0) then
Result := idade - pessoa.idade;
end;
Veja que TWPessoa herda de TWInterfacedObject - que fornece a função GetSelfObj - e também implementa a interface IWSortable, o quê exige a presença da função CompareTo para comparar 2 objetos. Veja também que esta função faz a comparação usando mais de um campo da classe pessoa - justamente porque consegue obter o ponteiro para a Pessoa original a partir da interface informada no parâmetro AObj.

Felizmente, a versão 2010 do Delphi lançada recentemente pela Embarcadero resolve nativamente esse problema. Nesta versão, ponteiros para interfaces passam a ser compatíveis com ponteiros para classes. Veja o post de Malcon Groves sobre o assunto aqui. Para versões antigas, a interface base ainda terá que ser usada.

O segundo questão a resolver é o fato das interfaces Delphi terem suas referências contadas (veja o post anterior). Depois que um objeto é atribuído a uma variável do tipo interface, o controle do ciclo de vida do objeto passa a ser feito automaticamente, significando que ele pode ser destruído antes do que você imagina. Como todo o resto no Delphi para Win32 tem ciclo controlado pelo programador, acredito que misturar os dois mecanismos traz mais confusão do que benefícios. Além de ser mais sujeito a erros.

Aproveitei então a classe base TWInterfacedObject e implementei nela os requisitos exigidos pelo tratamento de interfaces do Delphi. Isto é, escrevi minha versão para as funções do IUnknown de forma que que a contagem de referências é sempre 1, me deixando livre para destruir os objetos no ponto que eu julgar mais conveniente. A classe ficou como segue:
TWInterfacedObject = class(TObject, IWBaseInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function GetSelfObj : TObject;
end;
{...}
function TWInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;

function TWInterfacedObject._AddRef: Integer;
begin
Result := 1;
end;

function TWInterfacedObject._Release: Integer;
begin
Result := 1;
end;
Essas soluções apresentadas aqui funcionam bem se você pretende usar interfaces sem qualquer vínculo com a tecnologia COM. Caso contrário, é preferível utilizar os mecanismos padrões do Delphi pois eles foram construídos justamente para fornecer essa interação.

Mais Informações
Usando interfaces em Delphi - parte 1 e parte 2.

1 de setembro de 2009

Usando interfaces em Delphi - parte 2

Há alguma confusão quando se fala em interface em programação porque o termo remete à tecnologia COM da Microsoft e se mistura com ele. Colabora para a confusão o fato de que a interface base em Delphi tem o nome de IUnknown, o mesmo de uma interface amplamente utilizado no COM e que tem o mesmo objetivo, que é controlar a quantidade de referências a um objeto e também permitir que se descubra se esse objeto implementa ou não uma interface arbitrária. Na verdade, o projeto do COM é centrado no conceito de interfaces e, portanto, não poderia ter sido implementado no formato atual sem elas. E, quando o recurso de interfaces no Delphi foi desenhado, tinham como alvo a estrutura necessária para interagir com COM de modo que trabalhar com essa tecnologia fosse algo trivial.

Apesar de ter uma ligação tão intrincada com o COM, mostrei no post anterior que o conceito de interfaces em Delphi pode ser usado de forma desvinculada. Mesmo assim, a escolha de introduzir interfaces tendo o COM em mente produziu alguns efeitos colaterais que não podem ser negligenciados durante o projeto e programação de sistemas onde se queira usar esse recurso.

Em primeiro lugar, as interfaces em Delphi tem suas referências controladas através da implementação do IUnknown. Sempre que você atribui o ponteiro de um objeto ou de uma interface a uma variável de interface, o contador de referências é incrementado. Quando a variável de interface sai de escopo, o contador é automaticamente decrementado e quando chega a zero, o objeto original é removido da memória. Isso foge da forma tradicional de se controlar o ciclo de vida de um objeto em programas Win32 já que a implementação do IUnknown funciona como um garbage collector. Isto é, você cria o objeto que implementa uma interface mas é o compilador quem decide o momento em que o destructor será invocado.

No entanto, se o ponteiro do objeto que você criou jamais for atribuído a uma variável do tipo interface, você ainda é responsável por destruir esse objeto! É fácil ver a bagunça que vai virar se o planejamento não for bem feito, com memória sendo perdida ou erros de access violation sendo gerados do nada. Quando usado em conjunto com o COM, entretanto, a estrutura de criação através de CoClasses se encarregará de destruir corretamente o objeto.

Formas para resolver esse impasse podem ser encontradas num artigo publicado no EDN (Embarcadero Developer Network) : Delphi reference counted interfaces.

O segundo ponto com o qual se preocupar é com a necessidade de recuperar um ponteiro para a classe original a partir de um ponteiro da interface. Isto é, você instanciou a classe, atribuiu o ponteiro a uma interface e agora quer recuperar de novo o ponteiro para a classe. No Delphi, variáveis do tipo class são diferentes de variáveis do tipo interface e elas não são intercambiáveis como em outras linguagens de programação menos ligadas ao COM. De fato, uma variável para classe contem o endereço de memória onde estão os dados dessa classe enquanto que a interface é apenas um ponteiro para a tabela virtual de métodos dessa interface. São, portanto, incompatíveis e isso deve fazer parte do planejamento da hierarquia de classes e interfaces.

Por fim, as interfaces do Delphi funcionam melhor quando são vinculadas a um GUID pois a base para verificar se uma classe implementa uma determinada interface é a função QueryInterface introduzida pelo IUnknown e usada à exaustão quando se trabalha com interfaces. E essa função pede uma identificação única na forma de um GUID para a interface que se deseja pesquisar ou instanciar. Você pode pressionar as teclas <Ctrl-Shift-G> no IDE do Delphi para ter um novo GUID gerado. Ao declarar uma interface, inclua o GUID como no exemplo:
IWSortable = interface
['{7CEF3AE4-9127-460E-B1A1-8D18B7CA7D05}']
function CompareTo (Elemen: IWSortable): Integer;
end;

Volto em outra oportunidade com esse assunto para mostrar um implementação funcional de interface de sort.